Merge branch 'master' into pair_programming_section_121
10
.github/workflows/build.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
eval $(opam env)
|
||||
make install-dependencies
|
||||
make dependencies
|
||||
sudo apt update
|
||||
sudo apt install python3-dev python3-setuptools man2html rsync colordiff
|
||||
sudo python3 -m pip install --upgrade pip
|
||||
@ -54,18 +54,12 @@ jobs:
|
||||
run: |
|
||||
eval $(opam env)
|
||||
make build
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
eval $(opam env)
|
||||
make tests
|
||||
|
||||
- name: Make examples
|
||||
run: |
|
||||
eval $(opam env)
|
||||
make all_examples
|
||||
|
||||
- name: Make assets and documentation
|
||||
run: |
|
||||
eval $(opam env)
|
||||
make all
|
||||
make website-assets doc
|
||||
|
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
_build/
|
||||
_opam/
|
||||
*.install
|
||||
src/**/.merlin
|
||||
legifrance_oauth*
|
||||
|
108
CONTRIBUTING.md
@ -1,59 +1,34 @@
|
||||
# Contributing to Catala
|
||||
|
||||
The project is open to external contributions, in the spirit of open source. If you want to open a pull request, please follow the instructions below.
|
||||
The project is open to external contributions, in the spirit of open source.
|
||||
If you want to open a pull request, please follow the instructions below.
|
||||
|
||||
To ask a question to the Catala team, please open an issue on this repository. If you want to contribute to the project on a longer-term basis, or if you have specific competences as a socio-fiscal lawyer or a programming language specialist, please email the authors at denis.merigoux@inria.fr. The Catala team meets over visioconference once in a week.
|
||||
To ask a question to the Catala team, please open an issue on this repository.
|
||||
If you want to contribute to the project on a longer-term basis, or if you have
|
||||
specific competences as a socio-fiscal lawyer or a programming language specialist,
|
||||
please [contact the authors](mailto:contact@catala-lang.org).
|
||||
The Catala team meets over visioconference once every two weeks.
|
||||
|
||||
Please note that the copyright of this code is owned by Inria;
|
||||
by contributing, you disclaim all copyright interests in favor of Inria. Both the code for the compiler and the examples in this repository are distributed under the Apache2 license.
|
||||
|
||||
## Writing Catala examples
|
||||
|
||||
### Setting up the machinery
|
||||
|
||||
This section describes what to do to setup a working directory for a new Catala example, as well as the development cycle. Let us suppose that you want to create a new example named `foo`.
|
||||
|
||||
First, follow the instructions of [the installation readme](INSTALL.md) to get the compiler up and working up to `make build`. You can also set up the syntax highlighting for your editor.
|
||||
|
||||
Then, create the directory `examples/foo`. In there, create a master source file `foo.catala` that will be the root of your Catala program. You can then start programming in `foo.catala`, or split up your example into multiple files. In the later case, `foo.catala` must only contain something like this:
|
||||
|
||||
```
|
||||
@@Master file@@
|
||||
|
||||
@@Include: bar.catala@@
|
||||
```
|
||||
|
||||
where `examples/bar.catala` is another source file containing code for your example. Make sure you start by including some content in the source files, like
|
||||
|
||||
```
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
Now let's build the example, create a `Makefile` with the following contents:
|
||||
|
||||
```Makefile
|
||||
CATALA_LANG=en # or fr if your source code is in French
|
||||
SRC=foo.catala
|
||||
|
||||
include ../Makefile.common
|
||||
```
|
||||
|
||||
The `include` creates automatically all the targets you will need for your example. For instance, after making sure the compiler is built, you can launch
|
||||
|
||||
```
|
||||
make -C examples/foo foo.tex
|
||||
```
|
||||
|
||||
from the repository root to create the LaTeX weaving output of your source program. `Hello, world!` should appear in there.
|
||||
|
||||
Finally, please add a rule for your example in the repository root `Makefile` in the section "Examples-related rules", following the pattern for other examples. This will ensure that
|
||||
your example is built every time the compiler is modified; if a change in the compiler breaks your example, the authors will be notified and find a solution.
|
||||
by contributing, you disclaim all copyright interests in favor of Inria.
|
||||
Both the code for the compiler and the examples in this repository are
|
||||
distributed under the Apache2 license.
|
||||
|
||||
### Writing Catala code
|
||||
|
||||
Let us now present the typical Catala workflow. First, you need to locate the legislative text that you want to use as a reference. Then, simply copy-paste the text into your source file.
|
||||
Before writing Catala code, please read the
|
||||
[tutorial](https://catala-lang.org/en/examples/tutorial). You can run the
|
||||
programs of the tutorial yourself by following the instruction in the
|
||||
[README of the `examples` directory](examples/README.md). Then, it is suggested
|
||||
that you create a new example directory again according to the instructions of
|
||||
this README.
|
||||
|
||||
First you will have to format the copy-pasted text using Catala headings and articles markers:
|
||||
Let us now present the typical Catala workflow. First, you need to locate
|
||||
the legislative text that you want to use as a reference. Then, simply
|
||||
copy-paste the text into your source file.
|
||||
|
||||
First you will have to format the copy-pasted text using Catala headings
|
||||
and articles markers:
|
||||
|
||||
```
|
||||
@@Heading@@
|
||||
@ -63,14 +38,18 @@ First you will have to format the copy-pasted text using Catala headings and art
|
||||
@Legislative atom@
|
||||
```
|
||||
|
||||
Please look at the code of other examples to see how to format things properly. While formatting the text, don't forget regularly to try and parse your example using for instance
|
||||
Please look at the code of other examples to see how to format things properly.
|
||||
While formatting the text, don't forget regularly to try and parse your example
|
||||
using for instance
|
||||
|
||||
|
||||
```
|
||||
make -C examples/foo foo.tex
|
||||
```
|
||||
|
||||
to see if you've made any syntax errors. Once the text formatting is done, you can start to annotate each legislative atom (article, provision, etc.) with some Catala code. To open up a code section in Catala, simply use
|
||||
to see if you've made any syntax errors. Once the text formatting is done, you
|
||||
can start to annotate each legislative atom (article, provision, etc.) with
|
||||
some Catala code. To open up a code section in Catala, simply use
|
||||
|
||||
```
|
||||
/*
|
||||
@ -80,22 +59,45 @@ scope Foo:
|
||||
*/
|
||||
```
|
||||
|
||||
While all the code sections are equivalent in terms of execution, you can mark some as "metadata" so that they are printed differently on lawyer-facing documents. Here's how it works:
|
||||
While all the code sections are equivalent in terms of execution, you can
|
||||
mark some as "metadata" so that they are printed differently on lawyer-facing
|
||||
documents. Here's how it works:
|
||||
|
||||
```
|
||||
@@Begin metadata@@ # @@Début métadonnées@@ en français
|
||||
/*
|
||||
declaration structure FooBar:
|
||||
data foo content boolean
|
||||
data bar content amount
|
||||
data bar content money
|
||||
|
||||
<your structure/enumeration/scope declarations goes here>
|
||||
*/
|
||||
@@End metadata@@ # @@Fin métadonnées@@ en français
|
||||
```
|
||||
|
||||
Again, make sure to regularly check that your example is parsing correctly. The error message from the compiler should help you debug the syntax if need be.
|
||||
Again, make sure to regularly check that your example is parsing correctly. The error message from the compiler should help you debug the syntax if need be. You can also
|
||||
live-test the programs you wrote by feeding them to the interpreter
|
||||
(see the [README of the `examples` directory](examples/README.md)); this will
|
||||
also type-check the programs, which is useful for debugging them.
|
||||
|
||||
## Working on the compiler
|
||||
|
||||
The Catala compiler is a standard dune-managed OCaml project. You can look at the [online OCaml documentation](https://catala-lang.org/ocaml_docs/) for the different modules' interfaces.
|
||||
The Catala compiler is a standard dune-managed OCaml project.
|
||||
You can look at the
|
||||
[online OCaml documentation](https://catala-lang.org/ocaml_docs/) for the
|
||||
different modules' interfaces as well as high-level architecture documentation.
|
||||
|
||||
## Internationalization
|
||||
|
||||
The Catala language should be adapted to any legislative text that follows a
|
||||
general-to-specifics statutes order. Therefore, there exists multiple versions
|
||||
of the Catala surface syntax, adapted to the language of the legislative text.
|
||||
|
||||
Currently, Catala supports English and French legislative text via the
|
||||
`--language=en` or `--language=fr` option.
|
||||
|
||||
Technically, support for new languages can be added via a new lexer. If you want
|
||||
to add a new language, you can start from
|
||||
[existing lexer examples](src/catala/catala_surface/lexer_fr.ml), tweak and open
|
||||
a pull request. If you don't feel familiar enough with OCaml to do so, please
|
||||
leave an issue on this repository.
|
||||
|
68
INSTALL.md
@ -1,29 +1,26 @@
|
||||
# Installing the Catala compiler
|
||||
# Building and installing the Catala language
|
||||
|
||||
## Requirements
|
||||
|
||||
The Catala compiler is written using OCaml. To install OCaml on your machine and
|
||||
if you're running Linux ou MacOS, open a terminal and enter :
|
||||
The Catala compiler is written using OCaml. First, you have to install `opam`,
|
||||
OCaml's distribution and package manager. Follow the [instructions on the `opam`
|
||||
website](https://opam.ocaml.org/doc/Install.html).
|
||||
|
||||
./install_opam.sh
|
||||
|
||||
This will install `opam`, the OCaml dependency manager and the
|
||||
base OCaml compiler. If you're on Windows, the simplest solution
|
||||
would be to use Cygwin or the Windows Subsystem for Linux. Catala has been tested
|
||||
with OCaml version 4.09.1. You can switch to this version by typing :
|
||||
|
||||
opam switch create 4.09.1
|
||||
|
||||
or
|
||||
Next, you will need to use the correct version of OCaml. Catala has been tested
|
||||
with OCaml compiler versions that are at least 4.09.1. To switch to OCaml 4.09.1.,
|
||||
just use:
|
||||
|
||||
opam switch 4.09.1
|
||||
|
||||
if this version of OCaml is already installed. Next, install all the build
|
||||
dependencies with
|
||||
If you get a `No switch 4.09.1 is currently installed` error message, follow
|
||||
the hint and enter `opam switch create 4.09.1`.
|
||||
|
||||
make install-dependencies
|
||||
Next, install all the OCaml packages that Catala depend on, as well as some
|
||||
git submodules, with
|
||||
|
||||
This should ensure everything is set up for developping on the Catala compiler !
|
||||
make dependencies
|
||||
|
||||
This should ensure everything is set up for developping on the Catala compiler!
|
||||
|
||||
Other features for generation of files and literate programming also require
|
||||
the following executables to be present
|
||||
@ -41,7 +38,7 @@ On ArchLinux :
|
||||
|
||||
sudo pacman -S python-virtualenv man2html rsync
|
||||
|
||||
## Installation
|
||||
## Build
|
||||
|
||||
The project is distributed as a Dune artifact. Use standard dune commands to build
|
||||
and install the library. Makefile aliases are here to help you: running
|
||||
@ -50,12 +47,27 @@ and install the library. Makefile aliases are here to help you: running
|
||||
|
||||
builds the compiler from its OCaml sources.
|
||||
|
||||
## Install
|
||||
|
||||
The installation of the Catala compiler is handled through `opam`. Since the
|
||||
Catala compiler is not yet published to the `opam` repository, you can install
|
||||
a local version from this Git repository by using
|
||||
|
||||
opam install ./
|
||||
|
||||
To uninstall, use
|
||||
|
||||
opam unpin catala
|
||||
|
||||
### Generating website assets
|
||||
|
||||
The Catala website features assets generated by the Catala compiler. They are
|
||||
needed to build the website. To produce them, simply run from this repository's
|
||||
root directory
|
||||
needed to build the website. To produce them, simply run
|
||||
|
||||
make website-assets
|
||||
|
||||
Then, use a helper script to copy them over to the `assets` directory of the
|
||||
Catala website.
|
||||
|
||||
./generate_website_assets.sh <path-to-catala-website>/assets
|
||||
|
||||
@ -63,19 +75,6 @@ You will need the `man2html` executable to generate the HTML versions of the man
|
||||
pages, as well as the `rsync` executable to transfer files (preferred to `cp`)
|
||||
because it also works with a remote server.
|
||||
|
||||
### Opam package
|
||||
|
||||
If you want to install the library as an opam
|
||||
package, use the following command at the root of the repository:
|
||||
|
||||
opam install ./
|
||||
|
||||
You can then use the compiler with the `catala` command.
|
||||
|
||||
## Usage
|
||||
|
||||
Use `catala --help` to get more information about the command line options available.
|
||||
|
||||
## Syntax highlighting
|
||||
|
||||
The Catala language also comes with syntax highlighting to
|
||||
@ -119,7 +118,8 @@ augmented with the Catala plugin, simply enter
|
||||
make pygments
|
||||
|
||||
This will execute the
|
||||
script `syntax_highlighting/fr/pygments/set_up_pygments.sh` and `syntax_highlighting/en/pygments/set_up_pygments.sh`.
|
||||
script `syntax_highlighting/fr/pygments/set_up_pygments.sh` and
|
||||
`syntax_highlighting/en/pygments/set_up_pygments.sh`.
|
||||
|
||||
The scripts set up a virtual environement in
|
||||
`syntax_highlighting/fr/pygments/pygments/env` or
|
||||
|
89
Makefile
@ -9,23 +9,28 @@ K := $(foreach exec,$(EXECUTABLES),\
|
||||
$(if $(shell which $(exec)),some string,$(warning [WARNING] No "$(exec)" executable found. \
|
||||
Please install this executable for everything to work smoothly)))
|
||||
|
||||
install-dependencies-ocaml:
|
||||
dependencies-ocaml:
|
||||
opam install \
|
||||
ocamlformat \
|
||||
ANSITerminal \
|
||||
sedlex \
|
||||
menhir \
|
||||
menhirLib \
|
||||
dune dune-build-info \
|
||||
dune \
|
||||
cmdliner obelisk \
|
||||
re reason\
|
||||
obelisk\
|
||||
ocamlgraph
|
||||
re \
|
||||
obelisk \
|
||||
unionfind \
|
||||
bindlib \
|
||||
zarith zarith_stubs_js \
|
||||
ocamlgraph \
|
||||
js_of_ocaml-compiler js_of_ocaml js_of_ocaml-ppx \
|
||||
odate
|
||||
|
||||
init-submodules:
|
||||
git submodule update --init
|
||||
|
||||
install-dependencies: install-dependencies-ocaml init-submodules
|
||||
dependencies: dependencies-ocaml init-submodules
|
||||
|
||||
|
||||
##########################################
|
||||
@ -36,14 +41,18 @@ format:
|
||||
dune build @fmt --auto-promote | true
|
||||
|
||||
build:
|
||||
$(MAKE) -C src/catala/catala_surface parser_errors.ml
|
||||
$(MAKE) format
|
||||
@$(MAKE) --no-print-directory -C src/catala/catala_surface parser_errors.ml
|
||||
@$(MAKE) --no-print-directory format
|
||||
dune build
|
||||
|
||||
doc: build
|
||||
dune build @doc
|
||||
release_build:
|
||||
dune build --profile release
|
||||
|
||||
install: build
|
||||
doc:
|
||||
dune build @doc
|
||||
ln -sf $(PWD)/_build/default/_doc/_html/index.html doc/odoc.html
|
||||
|
||||
install:
|
||||
dune build @install
|
||||
|
||||
##########################################
|
||||
@ -103,25 +112,32 @@ EXAMPLES_DIR=examples
|
||||
ALLOCATIONS_FAMILIALES_DIR=$(EXAMPLES_DIR)/allocations_familiales
|
||||
CODE_GENERAL_IMPOTS_DIR=$(EXAMPLES_DIR)/code_general_impots
|
||||
US_TAX_CODE_DIR=$(EXAMPLES_DIR)/us_tax_code
|
||||
TUTORIAL_DIR=$(EXAMPLES_DIR)/tutorial
|
||||
TUTORIAL_EN_DIR=$(EXAMPLES_DIR)/tutorial_en
|
||||
TUTORIEL_FR_DIR=$(EXAMPLES_DIR)/tutoriel_fr
|
||||
|
||||
allocations_familiales: pygments build
|
||||
$(MAKE) -C $(ALLOCATIONS_FAMILIALES_DIR) $@.tex
|
||||
$(MAKE) -C $(ALLOCATIONS_FAMILIALES_DIR) $@.html
|
||||
|
||||
code_general_impots: pygments build
|
||||
$(MAKE) -C $(CODE_GENERAL_IMPOTS_DIR) $@.tex
|
||||
$(MAKE) -C $(CODE_GENERAL_IMPOTS_DIR) $@.html
|
||||
literate_allocations_familiales: pygments build
|
||||
$(MAKE) -C $(ALLOCATIONS_FAMILIALES_DIR) allocations_familiales.tex
|
||||
$(MAKE) -C $(ALLOCATIONS_FAMILIALES_DIR) allocations_familiales.html
|
||||
|
||||
us_tax_code: pygments build
|
||||
$(MAKE) -C $(US_TAX_CODE_DIR) $@.tex
|
||||
$(MAKE) -C $(US_TAX_CODE_DIR) $@.html
|
||||
literate_code_general_impots: pygments build
|
||||
$(MAKE) -C $(CODE_GENERAL_IMPOTS_DIR) code_general_impots.tex
|
||||
$(MAKE) -C $(CODE_GENERAL_IMPOTS_DIR) code_general_impots.html
|
||||
|
||||
tutorial_en: pygments build
|
||||
$(MAKE) -C $(TUTORIAL_DIR) $@.tex
|
||||
$(MAKE) -C $(TUTORIAL_DIR) $@.html
|
||||
literate_us_tax_code: pygments build
|
||||
$(MAKE) -C $(US_TAX_CODE_DIR) us_tax_code.tex
|
||||
$(MAKE) -C $(US_TAX_CODE_DIR) us_tax_code.html
|
||||
|
||||
all_examples: allocations_familiales code_general_impots us_tax_code tutorial_en
|
||||
literate_tutorial_en: pygments build
|
||||
$(MAKE) -C $(TUTORIAL_EN_DIR) tutorial_en.tex
|
||||
$(MAKE) -C $(TUTORIAL_EN_DIR) tutorial_en.html
|
||||
|
||||
literate_tutoriel_fr: pygments build
|
||||
$(MAKE) -C $(TUTORIEL_FR_DIR) tutoriel_fr.tex
|
||||
$(MAKE) -C $(TUTORIEL_FR_DIR) tutoriel_fr.html
|
||||
|
||||
literate_examples: literate_allocations_familiales literate_code_general_impots \
|
||||
literate_us_tax_code literate_tutorial_en literate_tutoriel_fr
|
||||
|
||||
##########################################
|
||||
# Execute test suite
|
||||
@ -129,8 +145,13 @@ all_examples: allocations_familiales code_general_impots us_tax_code tutorial_en
|
||||
|
||||
.FORCE:
|
||||
|
||||
tests: build .FORCE
|
||||
$(MAKE) -C tests
|
||||
test_suite: .FORCE
|
||||
@$(MAKE) --no-print-directory -C tests pass_tests
|
||||
|
||||
test_examples: .FORCE
|
||||
@$(MAKE) --no-print-directory -C examples tests
|
||||
|
||||
tests: test_suite test_examples
|
||||
|
||||
##########################################
|
||||
# Website assets
|
||||
@ -139,17 +160,17 @@ tests: build .FORCE
|
||||
grammar.html: src/catala/catala_surface/parser.mly
|
||||
obelisk html -o $@ $<
|
||||
|
||||
catala.html: src/catala/cli.ml
|
||||
catala.html: src/catala/utils/cli.ml
|
||||
dune exec src/catala.exe -- --help=groff | man2html | sed -e '1,8d' \
|
||||
| tac | sed "1,20d" | tac > $@
|
||||
|
||||
website-assets: doc all_examples grammar.html catala.html
|
||||
website-assets: doc literate_examples grammar.html catala.html release_build
|
||||
|
||||
##########################################
|
||||
# Misceallenous
|
||||
##########################################
|
||||
|
||||
all: install-dependencies build doc tests all_examples website-assets
|
||||
all: dependencies build doc tests literate_examples website-assets
|
||||
|
||||
clean:
|
||||
dune clean
|
||||
@ -159,11 +180,11 @@ clean:
|
||||
$(MAKE) -C $(CODE_GENERAL_IMPOTS_DIR) clean
|
||||
|
||||
inspect:
|
||||
gitinspector -f ml,mli,mly,iro,tex,catala,md,ir --grading
|
||||
gitinspector -f ml,mli,mly,iro,tex,catala,catala_en,catala_fr,md,fst,mld --grading
|
||||
|
||||
##########################################
|
||||
# Special targets
|
||||
##########################################
|
||||
.PHONY: inspect clean all all_examples english allocations_familiales pygments \
|
||||
install build format install-dependencies install-dependencies-ocaml \
|
||||
catala.html
|
||||
.PHONY: inspect clean all literate_examples english allocations_familiales pygments \
|
||||
install build doc format dependencies dependencies-ocaml \
|
||||
catala.html
|
||||
|
116
README.md
@ -1,27 +1,30 @@
|
||||
|
||||
<center>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/logo.png" alt="Catala logo" width="100"/>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/images/logo.png" alt="Catala logo" width="100"/>
|
||||
</center>
|
||||
|
||||
# Catala
|
||||
|
||||
Catala is a domain-specific language for deriving
|
||||
faithful-by-construction algorithms from legislative texts.
|
||||
faithful-by-construction algorithms from legislative texts. To learn quickly
|
||||
about the language and its features, you can jump right to the official
|
||||
[Catala tutorial](https://catala-lang.org/en/examples/tutorial).
|
||||
|
||||
## Concepts
|
||||
|
||||
Catala is a programming language adapted for socio-fiscal legislative literate programming. By annotating each line of the
|
||||
legislative text with its meaning in terms of code, one can derive
|
||||
an implementation of complex socio-fiscal mechanisms that enjoys
|
||||
a high level of assurance regarding the code-law faithfulness.
|
||||
Catala is a programming language adapted for socio-fiscal legislative literate
|
||||
programming. By annotating each line of the legislative text with its meaning
|
||||
in terms of code, one can derive an implementation of complex socio-fiscal
|
||||
mechanisms that enjoys a high level of assurance regarding the code-law
|
||||
faithfulness.
|
||||
|
||||
Concretely, you have to first gather all the laws, executive orders, previous cases, etc. that contain information about the
|
||||
socio-fiscal mechanism that you want to implement. Then, you can
|
||||
proceed to annotate the text article by article, in your favorite
|
||||
text editor :
|
||||
Concretely, you have to first gather all the laws, executive orders, previous
|
||||
cases, etc. that contain information about the socio-fiscal mechanism that
|
||||
you want to implement. Then, you can proceed to annotate the text article by
|
||||
article, in your favorite text editor :
|
||||
|
||||
<center>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/ScreenShotVSCode.png" alt="Screenshot" height="500"/>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/images/ScreenShotVSCode.png" alt="Screenshot" height="500"/>
|
||||
</center>
|
||||
|
||||
Once your code is complete and tested, you can use the Catala
|
||||
@ -32,72 +35,63 @@ can be reviewed and certified correct by the domain experts, which
|
||||
are in this case lawyers and not programmers.
|
||||
|
||||
<center>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/CatalaScreenShot.png" alt="Screenshot" height="500"/>
|
||||
<img src="https://github.com/CatalaLang/catala/raw/master/doc/images/CatalaScreenShot.png" alt="Screenshot" height="500"/>
|
||||
</center>
|
||||
|
||||
The Catala language is special because its logical structure mimics
|
||||
the logical structure of the law. Indeed, the core concept of
|
||||
"definition-under-conditions" that builds on default logic has been formalized by Professor of Law Sarah Lawsky in her article [A Logic for Statutes](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3088206). The Catala language is the only
|
||||
programming language to our knowledge that embeds default logic
|
||||
as a first-class feature, which is why it is the only language
|
||||
perfectly adapted to literate legislative programming.
|
||||
"definition-under-conditions" that builds on default logic has been formalized
|
||||
by Professor of Law Sarah Lawsky in her article
|
||||
[A Logic for Statutes](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3088206).
|
||||
The Catala language is the only programming language to our knowledge that
|
||||
embeds default logic as a first-class feature, which is why it is the only
|
||||
language perfectly adapted to literate legislative programming.
|
||||
|
||||
|
||||
## Catala motivating example : French "allocations familiales"
|
||||
|
||||
In the `example/allocations_familiales` folder, you will find the
|
||||
`allocations_familiales.catala` file which contains the
|
||||
algorithm computing French family benefits. The algorithm consists of annotations to the legislative
|
||||
texts that define the family benetifs, using the literate programming paradigm. The Catala
|
||||
compiler can extract from the `.catala` file a lawyer-readable version of the annotated text.
|
||||
|
||||
Currently, this lawyer-readable version comes in the form of a LaTeX document.
|
||||
You will need to have a standard LaTeX distribution installed as well as the
|
||||
`latexmk` build tool in order to enjoy the automated document generation process.
|
||||
|
||||
To get that lawyer-readable version (which is a LaTeX-created) PDF, simply use
|
||||
|
||||
make -C allocations_familiales allocations_familiales.pdf
|
||||
|
||||
from the repository root, once you have managed to install the
|
||||
compiler using [the dedicated readme](INSTALL.md). You can then open `examples/allocations_familiales/allocations_familiales.pdf`
|
||||
|
||||
## Languages
|
||||
|
||||
The Catala language should be adapted to any legislative text that follows a general-to-specifics statutes order. Therefore, there exists multiple versions of the Catala surface syntax, adapted to the language of the lefislative text.
|
||||
|
||||
Currently, Catala supports English and French legislations via the `--language=en` or `--language=fr` option. Contact the authors
|
||||
if you are interested in adding support for another language.
|
||||
|
||||
## Limitations and disclaimer
|
||||
|
||||
### Early stage project
|
||||
|
||||
Catala is a research project from Inria, the French National
|
||||
Research Institute for Computer Science. The compiler is yet very
|
||||
unstable and lacks most of its features. Currently, it only
|
||||
parses the surface language to producde the lawyer-readable PDF,
|
||||
no interpreter or compiler backend is provided.
|
||||
|
||||
However, the language is bound to have a complete formal semantics
|
||||
in the near future. This semantics will guide the compiler
|
||||
implementation.
|
||||
|
||||
## Installation
|
||||
## Building and installation
|
||||
|
||||
See [the dedicated readme](INSTALL.md).
|
||||
|
||||
## Test suite
|
||||
## Usage
|
||||
|
||||
See [the dedicated readme](tests/README.md).
|
||||
Use `catala --help` to get more information about the command line
|
||||
options available.
|
||||
|
||||
## Examples
|
||||
|
||||
See [the dedicated readme](examples/README.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [the dedicated readme](CONTRIBUTING.md).
|
||||
|
||||
## Test suite
|
||||
|
||||
See [the dedicated readme](tests/README.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
### Formal semantics
|
||||
|
||||
See [the dedicated readme](doc/formalization/README.md).
|
||||
|
||||
### Compiler documentation
|
||||
|
||||
The compiler documentation is auto-generated from its source code using
|
||||
`dune` and `odoc`. Use
|
||||
|
||||
make doc
|
||||
|
||||
to generate the documentation, then open the `doc/odoc.html` file in any browser.
|
||||
|
||||
## License
|
||||
|
||||
The library is released under the Apache license (version 2).
|
||||
The library is released under the [Apache license (version 2)](LICENSE.txt).
|
||||
|
||||
## Limitations and disclaimer
|
||||
|
||||
Catala is a research project from Inria, the French National
|
||||
Research Institute for Computer Science. The compiler is yet
|
||||
unstable and lacks some of its features.
|
||||
|
||||
## Pierre Catala
|
||||
|
||||
|
16
catala.opam
@ -1,27 +1,31 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
version: "0.1.1"
|
||||
version: "0.2.0"
|
||||
synopsis: "Low-level language for tax code specification"
|
||||
description: """
|
||||
The Catala language is designed to be a low-level target for
|
||||
higher-level specification languages for fiscal legislation.
|
||||
"""
|
||||
maintainer: ["denis.merigoux@inria.fr"]
|
||||
maintainer: ["contact@catala-lang.org"]
|
||||
authors: ["Denis Merigoux"]
|
||||
license: "Apache2"
|
||||
license: "Apache-2.0"
|
||||
homepage: "https://github.com/CatalaLang/catala"
|
||||
bug-reports: "https://github.com/CatalaLang/catala/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.07.0"}
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"ANSITerminal" {>= "0.8.2"}
|
||||
"sedlex" {>= "2.1"}
|
||||
"menhir" {>= "20200211"}
|
||||
"menhirLib" {>= "20200211"}
|
||||
"dune-build-info" {>= "2.0.1"}
|
||||
"unionFind" {>= "20200320"}
|
||||
"bindlib" {>= "5.0.1"}
|
||||
"cmdliner" {>= "1.0.4"}
|
||||
"re" {>= "1.9.0"}
|
||||
"dune" {build}
|
||||
"zarith" {>= "1.10"}
|
||||
"zarith_stubs_js" {>= "0.14.0"}
|
||||
"dune" {>= "2.2"}
|
||||
"ocamlgraph" {>= "1.8.8"}
|
||||
"odate" {>= "0.6"}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {pinned}
|
||||
|
Before Width: | Height: | Size: 128 KiB |
10
doc/formalization/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# The Catala formalization
|
||||
|
||||
This folder contains the LaTeX sources of the document describing the
|
||||
formalization the Catala programming language. To build the PDF output,
|
||||
simply invoke:
|
||||
|
||||
make formalization
|
||||
|
||||
The directory also contains the F* sources of the proof of type soundness
|
||||
of this formalization.
|
BIN
doc/formalization/formalization.pdf
Normal file
@ -175,7 +175,7 @@ lambda-calculus extensions (such as algebraic data types or $\Lambda$-polymorphi
|
||||
&&\synalt&\synlambda\synlparen\synvar{x}\syntyped\synvar{\tau}\synrparen\syndot\synvar{e}\synalt\synvar{e}\;\synvar{e}&$\lambda$-calculus\\
|
||||
&&\synalt&\synvar{d}&default term\\
|
||||
&&&&\\
|
||||
Default&\synvar{d}&\syndef&\synlangle\synvar{e}\synjust\synvar{e}\synmid $[\synvar{e}^*]$\synrangle&default term\\
|
||||
Default&\synvar{d}&\syndef&\synlangle $[\synvar{e}^*] \synmid\synvar{e}\synjust\synvar{e}$\synrangle&default term\\
|
||||
&&\synalt&\synerror&conflict error term\\
|
||||
&&\synalt&\synemptydefault&empty error term\\
|
||||
\end{tabular}
|
||||
@ -183,7 +183,8 @@ lambda-calculus extensions (such as algebraic data types or $\Lambda$-polymorphi
|
||||
|
||||
Compared to the regular lambda calculus, we add a construction coming from
|
||||
default logic. Particularly, we focus on a subset of default logic called
|
||||
categorical, prioritized default logic \cite{Brewka2000}. In this setting, a default is a logical
|
||||
categorical, prioritized default logic \cite{Brewka2000}.
|
||||
In this setting, a default is a logical
|
||||
rule of the form $A \synjust B$ where $A$ is the justification of the rule and
|
||||
$B$ is the consequence. The rule can only be applied if $A$ is consistent with
|
||||
the current knowledge $W$: from $A\wedge W$, one should not derive $\bot$.
|
||||
@ -196,19 +197,18 @@ be an expression that can be evaluated to \syntrue{} or \synfalse{}, and $B$
|
||||
the expression that the default should reduce to if $A$ is true. If $A$ is false,
|
||||
then we look up for other rules of lesser priority to apply. This priority
|
||||
is encoded trough a syntactic tree data structure\footnote{Thanks to Pierre-Évariste Dagand for this insight.}.
|
||||
A node of the tree contains a default to consider first, and then a list of lower-priority
|
||||
defaults that don't have a particular ordering between them. This structure is
|
||||
A node of the tree contains a base case to consider, but first a list of higher-priority
|
||||
exceptions that don't have a particular ordering between them. This structure is
|
||||
sufficient to model the base case/exceptions structure or the law, and in particular
|
||||
the fact that exceptions are not always prioritized in the legislative text.
|
||||
|
||||
In the term \synlangle\synvar{e_{\text{just}}}\synjust
|
||||
\synvar{e_{\text{cons}}}\synmid \synvar{e_1}\synellipsis\synvar{e_n}\synrangle, \synvar{e_{\text{just}}}
|
||||
In the term \synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid\synvar{e_{\text{just}}}\synjust
|
||||
\synvar{e_{\text{cons}}} \synrangle, \synvar{e_{\text{just}}}
|
||||
is the justification $A$, \synvar{e_{\text{cons}}} is the consequence $B$ and
|
||||
\synvar{e_1}\synellipsis\synvar{e_n} is the list of rules to be considered if \synvar{e_{\text{just}}}
|
||||
evaluates to \synfalse{}.
|
||||
\synvar{e_1}\synellipsis\synvar{e_n} are the list of exceptions to be considered first.
|
||||
|
||||
Of course, this evaluation scheme can fail if no more
|
||||
rules can be applied, or if two or more rules of the same priority have their
|
||||
rules can be applied, or if two or more exceptions of the same priority have their
|
||||
justification evaluate to \syntrue{}. The error terms \synerror{} and \synemptydefault{}
|
||||
encode these failure cases. Note that if a Catala program correctly derived from a legislative
|
||||
source evaluates to \synerror{} or \synemptydefault{}, this could mean a flaw in the
|
||||
@ -275,14 +275,15 @@ of the default should be typed.
|
||||
\begin{mathpar}
|
||||
\inferrule[T-Default]
|
||||
{
|
||||
\typctx{\Gamma}\typvdash\synvar{e_{\text{just}}}\typcolon\synbool\\
|
||||
\typctx{\Gamma}\typvdash\synvar{e_{\text{cons}}}\typcolon\synvar{\tau}\\
|
||||
\typctx{\Gamma}\typvdash\synvar{e_1}\typcolon{\tau}\\
|
||||
\cdots\\
|
||||
\typctx{\Gamma}\typvdash\synvar{e_n}\typcolon{\tau}
|
||||
\typctx{\Gamma}\typvdash\synvar{e_n}\typcolon{\tau}\\
|
||||
\typctx{\Gamma}\typvdash\synvar{e_{\text{just}}}\typcolon\synbool\\
|
||||
\typctx{\Gamma}\typvdash\synvar{e_{\text{cons}}}\typcolon\synvar{\tau}
|
||||
}
|
||||
{\typctx{\Gamma}\typvdash\synlangle\synvar{e_{\text{just}}}\synjust\synvar{e_{\text{cons}}}\synmid
|
||||
\synvar{e_1}\synellipsis\synvar{e_n}\synrangle\typcolon\synvar{\tau}}
|
||||
{\typctx{\Gamma}\typvdash\synlangle
|
||||
\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_{\text{just}}}\synjust\synvar{e_{\text{cons}}}\synrangle\typcolon\synvar{\tau}}
|
||||
\end{mathpar}
|
||||
|
||||
The situation becomes more complex in the presence of functions. Indeed, want
|
||||
@ -307,11 +308,11 @@ currently being reduced.
|
||||
&&\synalt&\syntrue\synalt\synfalse & booleans\\
|
||||
&&\synalt&\synerror\synalt\synemptydefault&errors\\
|
||||
Evaluation &\synvar{C_\lambda}&\syndef&\synhole\;\synvar{e}\synalt\synvar{v}\;\synhole&function application\\
|
||||
contexts&&\synalt&\synlangle\synhole\synjust\synvar{e}\synmid $[\synvar{e}^*]$\synrangle&default justification evaluation\\
|
||||
contexts&&\synalt&\synlangle$[\synvar{v}^*]$\syncomma\synhole\syncomma$[\synvar{e}^*]$\synmid
|
||||
\synvar{e}\synjust\synvar{e}\synrangle&default exceptions evaluation\\
|
||||
&\synvar{C}&\syndef&\synvar{C_\lambda}®ular contexts\\
|
||||
&&\synalt&\synlangle\syntrue\synjust\synhole\synmid $[\synvar{e}^*]$\synrangle&default consequence evaluation\\
|
||||
&&\synalt&\synlangle\synfalse\synjust\synvar{e}\synmid $[\synvar{v}^*]$%
|
||||
\syncomma\synhole\syncomma$[\synvar{e}^*]$\synrangle&sub-default evaluation
|
||||
&&\synalt&\synlangle$[\synvar{v}^*]$\synmid\synhole\synjust\synvar{e}\synrangle&default justification evaluation\\
|
||||
&&\synalt&\synlangle$[\synvar{v}^*]$\synmid\syntrue\synjust\synhole \synrangle&default consequence evaluation\\
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
@ -331,70 +332,55 @@ later.
|
||||
}
|
||||
\end{mathpar}
|
||||
|
||||
Now we have to describe how the default terms reduce. Thanks to a the
|
||||
\TirName{D-Context} rule, we can suppose that the justification of the default
|
||||
is already reduced to a variable \synvar{v}. By applying the beta-reduction
|
||||
rule \TirName{D-$\beta$}, we can further reduce to the case where \synvar{v} is a boolean.
|
||||
This is where we encode our default logic evaluation semantics. If \synvar{v} is
|
||||
\syntrue{}, then this rule applies and we reduce to the consequence. We don't
|
||||
even have to consider rules of lower priority lower in the tree. This behavior
|
||||
is similar to short-circuit reduction rules of boolean operators, that enable
|
||||
a significant performance gain at execution.
|
||||
\begin{mathpar}
|
||||
\inferrule[D-DefaultTrueNoError]
|
||||
{v\neq\synemptydefault}
|
||||
{\synlangle \syntrue\synjust \synvar{v}\synmid \synvar{e_1}\synellipsis\synvar{e_n}\synrangle\exeval v}
|
||||
\end{mathpar}
|
||||
|
||||
However, if the consequence of the first default evaluates to \synemptydefault,
|
||||
then we fall back on the rules of lower priority, as if the justification had
|
||||
evaluated to \synfalse. This behavior is useful when the consequence of the
|
||||
first default is itself a \enquote{high-priority} default tree. In this case,
|
||||
\synvar{e_1}\synellipsis\synvar{e_n} acts a the \enquote{low-priority} default
|
||||
tree. The chaining
|
||||
behavior from the high-priority tree to the low-priority tree
|
||||
defined by \TirName{D-DefaultTrueError}, will be very useful in
|
||||
\sref{scope:formalization}.
|
||||
\begin{mathpar}
|
||||
\inferrule[D-DefaultTrueError]
|
||||
{}
|
||||
{
|
||||
\synlangle \syntrue\synjust \synemptydefault\synmid \synvar{e_1}\synellipsis\synvar{e_n}\synrangle\exeval
|
||||
\synlangle \synfalse\synjust \synemptydefault\synmid \synvar{e_1}\synellipsis\synvar{e_n} \synrangle
|
||||
}
|
||||
\end{mathpar}
|
||||
|
||||
If the consequence of the default is \synfalse{}, then we have to consider rules of lower priority
|
||||
\synvar{e_1}\synellipsis\synvar{e_n} that should be all evaluated (left to right),
|
||||
Now we have to describe how the default terms reduce. First, we consider
|
||||
the list of exceptions to the default,
|
||||
\synvar{e_1}\synellipsis\synvar{e_n}, that should be all evaluated (left to right),
|
||||
according to the sub-default evaluation context. Then, we consider all the
|
||||
values yielded by the sub-default evaluation and define two functions over these
|
||||
values yielded by the exception evaluation and define two functions over these
|
||||
values. Let $\exeemptysubdefaults(\synvar{v_1}\synellipsis\synvar{v_n})$ returns
|
||||
the number of empty error terms \synemptydefault{} among the values. We then case analyze on this count:
|
||||
the number of empty error terms \synemptydefault{} among the exception values.
|
||||
We then case analyze on this count:
|
||||
\begin{itemize}
|
||||
\item if $\exeemptysubdefaults(\synvar{v_1}\synellipsis\synvar{v_n}) =n$, then
|
||||
none of the sub-defaults apply and we return \synemptydefault;
|
||||
none of the exceptions apply and we evaluate the base case;
|
||||
\item if $\exeemptysubdefaults(\synvar{v_1}\synellipsis\synvar{v_n}) =n - 1$,
|
||||
then only only one of the sub-default apply and we return its corresponding value;
|
||||
then only only one of the exceptions apply and we return its corresponding value;
|
||||
\item if $\exeemptysubdefaults(\synvar{v_1}\synellipsis\synvar{v_n}) < n - 1$,
|
||||
then two or more sub-default apply and we raise a conflict error \synerror.
|
||||
then two or more exceptions apply and we raise a conflict error \synerror.
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\begin{mathpar}
|
||||
\inferrule[D-DefaultFalseNoSub]
|
||||
\inferrule[D-DefaultFalseNoExceptions]
|
||||
{}
|
||||
{\synlangle \synfalse\synjust \synvar{e}\synmid \synemptydefault{}\synellipsis\synemptydefault{}\synrangle\exeval \synemptydefault{}}
|
||||
{\synlangle \synemptydefault{}\synellipsis\synemptydefault{}\synmid\synfalse\synjust \synvar{e} \synrangle\exeval \synemptydefault{}}
|
||||
|
||||
\inferrule[D-DefaultFalseOneSub]
|
||||
\inferrule[D-DefaultTrueNoExceptions]
|
||||
{}
|
||||
{\synlangle \synfalse\synjust \synvar{e}\synmid
|
||||
\synemptydefault\synellipsis\synemptydefault\syncomma\synvar{v}\syncomma\synemptydefault\synellipsis\synemptydefault\synrangle\exeval \synvar{v}}
|
||||
{\synlangle \synvar{e_1}\synellipsis\synvar{e_n}\synmid\syntrue\synjust \synvar{v}\synrangle\exeval v}
|
||||
|
||||
\inferrule[D-DefaultFalseSubConflict]
|
||||
|
||||
\inferrule[D-DefaultOneException]
|
||||
{}
|
||||
{\synlangle \synemptydefault\synellipsis\synemptydefault\syncomma\synvar{v}\syncomma\synemptydefault\synellipsis\synemptydefault
|
||||
\synmid \synvar{e_1}\synjust \synvar{e_2}
|
||||
\synrangle\exeval \synvar{v}}
|
||||
|
||||
\inferrule[D-DefaultExceptionsConflict]
|
||||
{\exeemptysubdefaults(\synvar{v_1}\synellipsis\synvar{v_n}) <n - 1}
|
||||
{\synlangle \synfalse\synjust \synvar{e}\synmid \synvar{v_1}\synellipsis\synvar{v_n}\synrangle\exeval \synerror{}}
|
||||
{\synlangle \synvar{v_1}\synellipsis\synvar{v_n}\synmid
|
||||
\synvar{e_1}\synjust \synvar{e_2}\synrangle\exeval \synerror{}}
|
||||
\end{mathpar}
|
||||
|
||||
When none of the exceptions apply, we can suppose that the justification of the default
|
||||
is already reduced to a variable \synvar{v}, which should be a boolean by virtue of
|
||||
typing. If \synvar{v} is
|
||||
\syntrue{}, then this rule applies and we reduce to the consequence. If it is
|
||||
\synfalse{}, then the base case does not apply either and we throw an empty
|
||||
default error.
|
||||
|
||||
|
||||
|
||||
Last, we need to define how our error terms propagate. Because the rules for
|
||||
sub-default evaluation have to count the number of error terms in the list
|
||||
of sub-defaults, we cannot always immediately propagate the error term \synemptydefault{} in
|
||||
@ -468,8 +454,9 @@ sub-call and give them different names $\synvar{S'}_1$,
|
||||
Expression&\synvar{e}&\syndef&\synvar{\ell}&location\\
|
||||
&&\synalt&$\cdots$&default calculus expressions\\
|
||||
&&&&\\
|
||||
Rule&\synvar{r}&\syndef&\synrule\synvar{\ell}\syntyped\synvar{\tau}\synequal\synlangle\synvar{e}\synjust
|
||||
\synvar{e}\synmid$[\synvar{e}^*]$\synrangle
|
||||
Rule&\synvar{r}&\syndef&\synrule\synvar{\ell}\syntyped\synvar{\tau}\synequal\synlangle
|
||||
$[\synvar{e}^*]$\synmid\synvar{e}\synjust
|
||||
\synvar{e}\synrangle
|
||||
&Location definition\\
|
||||
&&\synalt&\syncall$\synvar{S}_\synvar{n}$&sub-scope call\\
|
||||
Scope declaration&\synvar{\sigma}&\syndef&\synscope\synvar{S}\syntyped $[\synvar{r}^*]$&\\
|
||||
@ -484,13 +471,13 @@ Let's illustrate how the scope language plays out with a simple program
|
||||
that calls a sub-scope:
|
||||
\begin{Verbatim}[frame=lines,label=Simple scope program, numbers=left, framesep=10pt, samepage=true]
|
||||
scope X:
|
||||
rule a = < true :- 0 | >
|
||||
rule b = < true :- a + 1 | >
|
||||
rule a = < true :- 0 >
|
||||
rule b = < true :- a + 1 >
|
||||
|
||||
scope Y:
|
||||
rule X_1[a] = < true :- 42 | >
|
||||
rule X_1[a] = < true :- 42 >
|
||||
call X_1
|
||||
rule c = < X_1[b] == 1 :- false | >, < X_1[b] == 43 :- true | >
|
||||
rule c = < X_1[b] != 43 :- false | X_1[b] == 43 :- true >
|
||||
\end{Verbatim}
|
||||
|
||||
Considered alone, the execution \Verb+X+ is simple: \Verb+a+ and \Verb+b+ are defined by
|
||||
@ -498,7 +485,7 @@ a single default whose justification is \Verb+true+. Hence, \Verb+a+ should eval
|
||||
to \Verb+0+ and \Verb+b+ should evaluate to \Verb+1+.
|
||||
|
||||
Now, consider scope \Verb+Y+. It defines a single variable \Verb+c+ with two defaults
|
||||
lines 8 and 9, but the justifications for these two defaults use the result of
|
||||
line 8, but the justifications for these two defaults use the result of
|
||||
the evaluation (line 7) of variable \Verb+b+ of the sub-scope \Verb+X_1+.
|
||||
Line 6 shows an example of providing an \enquote{argument} to the subscope call.
|
||||
The execution goes like this: at line 7 when calling the sub-scope,
|
||||
@ -506,7 +493,9 @@ The execution goes like this: at line 7 when calling the sub-scope,
|
||||
from line 6. Because the caller has priority over the callee, the default from line
|
||||
6 wins and \Verb+X_1[a]+ evaluates to \Verb+42+. Consequently,
|
||||
\Verb+X_1[b]+ evaluates to \Verb+43+.
|
||||
This triggers the second default in the list of line 9 which evaluates \Verb+c+ to \Verb+true+.
|
||||
This triggers the second default in the list of line 8: the exception
|
||||
evaluates first, but does not apply. Then, the base case applies,
|
||||
and evaluates \Verb+c+ to \Verb+true+.
|
||||
|
||||
The goal is to provide an encoding of the scope language
|
||||
into the lambda calculus that is compatible with this intuitive description
|
||||
@ -515,44 +504,39 @@ picture of the translation, we first show what the previous simple program will
|
||||
to, using ML-like syntax for the target default calculus:
|
||||
\begin{Verbatim}[frame=lines,label=Simple default program, numbers=left, framesep=10pt, samepage=true]
|
||||
let X (a: unit -> int) (b: unit -> int) : (int * int) =
|
||||
let a : unit -> int = a ++ (fun () -> < true :- 0 | >) in
|
||||
let a : int = a () in
|
||||
let b : unit -> int = b ++ (fun () -> < true :- a + 1 | >) in
|
||||
let b : int = b () in
|
||||
let a : int = < a () | < true :- 0 >> in
|
||||
let b : int = < b () | < true :- a + 1 >> in
|
||||
(a, b)
|
||||
|
||||
let Y (c: unit -> bool) : bool =
|
||||
let X_1[a] : unit -> int = fun () -> < true :- 42 | > in
|
||||
let X_1[a] : unit -> int = fun () -> < true :- 42 > in
|
||||
let X_1[b] : unit -> int = fun () -> EmptyError in
|
||||
let (X_1[a], X_1[b]) : int * int = X(X_1[a], X_1[b]) in
|
||||
let c : unit -> bool = c ++ (fun () ->
|
||||
< X_1[b] == 1 :- false>, < X_1[b] == 43 :- true >)
|
||||
in
|
||||
let c : bool = c () in
|
||||
let c : bool = < c () | < X_1[b] != 43 :- false | X_1[b] == 43 :- true >> in
|
||||
c
|
||||
\end{Verbatim}
|
||||
|
||||
|
||||
We start unravelling this translation with the scope \Verb+X+. \Verb+X+ has
|
||||
been turned into a function whose arguments are all the local variables of the
|
||||
scope. However, the arguments have type \Verb+unit -> <type>+. Indeed, we want the
|
||||
arguments of \Verb+X+ (line 1 ) to be the default expression supplied by the caller of
|
||||
\Verb+X+, which will later be merged with a higher priority (operator \Verb|++|) to the default
|
||||
expression defining the local variables of \Verb+X+ (lines 2 and 4). But since defaults are not
|
||||
values in our default calculus, we have to thunk them in the arguments of \Verb|X|
|
||||
so that their evaluation is delayed. After the defaults have been merged, we apply
|
||||
\Verb+()+ to the thunk (lines 3 and 5) to force evaluation and get back the value.
|
||||
Finally, \Verb+X+ returns the tuple of all its local variables (line 6).
|
||||
arguments of \Verb+X+ (line 1) to be the default expression supplied by the caller of
|
||||
\Verb+X+, which are considered as exceptions to the base
|
||||
expression defining the local variables of \Verb+X+ (lines 2 and 3).
|
||||
After the merging of scope-local and
|
||||
scope-arguments defaults, we apply
|
||||
\Verb+()+ to the thunk to force evaluation and get back the value.
|
||||
Finally, \Verb+X+ returns the tuple of all its local variables (line 4).
|
||||
|
||||
The translation of \Verb+Y+ exhibits the pattern for sub-scope calls.
|
||||
Lines 9 translates the assignment of the sub-scope argument \Verb+X_1[a]+.
|
||||
Before calling \Verb+X_1+ (line 11), the other argument \Verb+X_1[b]+ is
|
||||
Lines 7 translates the assignment of the sub-scope argument \Verb+X_1[a]+.
|
||||
Before calling \Verb+X_1+ (line 8), the other argument \Verb+X_1[b]+ is
|
||||
initialized to the neutral \synemptydefault{} that will be ignored at execution
|
||||
because \Verb+X+ provides more defaults for \Verb+b+.
|
||||
The sub-scope call is translated to a regular
|
||||
function call (line 11). The results of the call are then used in the two defaults
|
||||
for \Verb+c+ (lines 13), which have been turned into a default tree taking into
|
||||
account that no priority has been declared between the two defaults.
|
||||
Finally, \Verb+c+ is evaluated (line 15).
|
||||
function call (line 9). The results of the call are then used in the two defaults
|
||||
for \Verb+c+ (line 10), which have been turned into a default tree taking into
|
||||
account the possible input for \Verb+c+.
|
||||
|
||||
\subsection{Formalization of the translation}
|
||||
\label{sec:scope:formalization}
|
||||
@ -639,17 +623,21 @@ with the definitions of scope variables.
|
||||
\begin{mathpar}
|
||||
\inferrule[T-DefScopeVar]{
|
||||
\synvar{a}\notin\redctx{\Delta}\\
|
||||
\redctx{\Delta}\typvdash\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle\typcolon\synvar{\tau}
|
||||
\redctx{\Delta}\typvdash
|
||||
\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle\typcolon\synvar{\tau}
|
||||
}{
|
||||
\synvar{P}\redsc\redctx{\Delta}\redturnstile{\synvar{S}}
|
||||
\synrule\synvar{a}\syntyped\synvar{\tau}\synequal
|
||||
\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle
|
||||
\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle
|
||||
\reduces \\
|
||||
\synlet a\syntyped\synvar{\tau}\synequal \synlparen\synvar{a} \synmerge
|
||||
\synlambda \synlparen \synunit\syntyped\synunitt\synrparen\syndot
|
||||
\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle
|
||||
\synrparen\;\synunit\synin\synhole
|
||||
\redproduce\synvar{a}\redcolon\synvar{\tau}\redcomma\redctx{\Delta}
|
||||
\synlet a\syntyped\synvar{\tau}\synequal
|
||||
\synlangle a\;\synunit\synmid
|
||||
\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle
|
||||
\synrangle\synin\synhole
|
||||
\redproduce\synvar{a}\redcolon\synvar{\tau}\redcomma\redctx{\Delta}
|
||||
}
|
||||
\end{mathpar}
|
||||
|
||||
@ -672,26 +660,12 @@ which seeds the typing judgment of \sref{defaultcalc:typing} with \redctx{\Delta
|
||||
Since scope variables are also arguments of the scope, \TirName{T-DefScopeVar}
|
||||
redefines \synvar{a} by merging the new default tree with the default expression
|
||||
\synvar{a} of type \synunitt\synarrow\synvar{\tau} passed as an argument to \synvar{S}.
|
||||
The merging operator,\synmerge, has the following definition :
|
||||
\begin{align*}
|
||||
\synlet \synlparen\synmerge\synrparen\;&
|
||||
\synlparen\synvar{e_1}\syntyped\synunitt\synarrow\synvar{\tau}\synrparen\;
|
||||
\synlparen\synvar{e_2}\syntyped\synunitt\synarrow\synvar{\tau}\synrparen\syntyped
|
||||
\synunitt\synarrow\synvar{\tau}
|
||||
\synequal\\
|
||||
&\synlangle\synlambda\synlparen\synunit\syntyped\synunitt\synrparen\syndot\syntrue\synjust
|
||||
\synvar{e_1}\synmid\synvar{e_2}\synrangle
|
||||
\end{align*}
|
||||
This merging is done by defining the incoming argument as an exception to the
|
||||
scope-local expression. This translation scheme ensures that the caller always
|
||||
has priority over the callee. The evaluation of the incoming arguments is forced by applying \synunit,
|
||||
yielding a value of type \synvar{\tau} for \synvar{a}.
|
||||
|
||||
\synmerge is asymetric in terms of priority: in \synvar{e_1}\synmerge\synvar{e_2},
|
||||
\synvar{e_1} will have the highest priority and it is only in the case where
|
||||
none of the rules in \synvar{e_1} apply that \synvar{e_2} will be considered.
|
||||
In \TirName{T-DefScopeVar}, the left-hand-side of \synmerge is coherent with
|
||||
our objective of giving priority to the caller.
|
||||
|
||||
Finally, the evaluation of the merged default tree is forced by applying \synunit,
|
||||
yielding a value of type \synvar{\tau} for \synvar{a} which will be available
|
||||
for use in the rest of the translated program. Now that we have presented the
|
||||
ow that we have presented the
|
||||
translation scheme for rules defining scope variables, we can switch to the
|
||||
translation of sub-scope variables definitions and calls. We will start by
|
||||
the rules that define sub-scope variables, prior to calling the associated
|
||||
@ -700,16 +674,19 @@ sub-scope.
|
||||
\inferrule[T-DefSubScopeVar]{
|
||||
S\neq S'\\
|
||||
\synvar{S'}_\synvar{n}\synlsquare\synvar{a}\synrsquare\notin\redctx{\Delta}\\
|
||||
\redctx{\Delta}\typvdash\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle
|
||||
\redctx{\Delta}\typvdash\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle
|
||||
\typcolon\synvar{\tau}
|
||||
}{
|
||||
\synvar{P}\redsc\redctx{\Delta}\redturnstile{\synvar{S}}
|
||||
\synrule\synvar{S'}_\synvar{n}\synlsquare\synvar{a}\synrsquare\syntyped\synvar{\tau}\synequal
|
||||
\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle
|
||||
\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle
|
||||
\reduces \\
|
||||
\synlet \synvar{S'}_\synvar{n}\synlsquare\synvar{a}\synrsquare\syntyped\synunitt\synarrow\synvar{\tau}\synequal
|
||||
\synlambda \synlparen\synunit\syntyped\synunitt\synrparen\syndot
|
||||
\synlangle\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synmid\synvar{e_1}\synellipsis\synvar{e_n}\synrangle
|
||||
\synlangle\synvar{e_1}\synellipsis\synvar{e_n}\synmid
|
||||
\synvar{e_\mathrm{just}}\synjust\synvar{e_\mathrm{cons}}\synrangle
|
||||
\synin\synhole\redproduce\\\synvar{S'}_\synvar{n}\synlsquare\synvar{a}\synrsquare\redcolon\synunitt\synarrow\synvar{\tau}\redcomma\redctx{\Delta}
|
||||
}
|
||||
\end{mathpar}
|
||||
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
BIN
doc/images/ScreenShotVSCode.png
Normal file
After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -157,7 +157,7 @@ Current proposal : "amount".
|
||||
The base data type for representing amounts of money.
|
||||
|
||||
```
|
||||
data foo content amount
|
||||
data foo content money
|
||||
```
|
||||
|
||||
## TEXT
|
21
dune-project
@ -1,14 +1,16 @@
|
||||
(lang dune 2.2)
|
||||
|
||||
(name catala)
|
||||
(version 0.1.1)
|
||||
(version 0.2.0)
|
||||
(generate_opam_files true)
|
||||
(formatting)
|
||||
|
||||
(source (uri git+https://github.com/CatalaLang/catala.git))
|
||||
(homepage https://github.com/CatalaLang/catala)
|
||||
(bug_reports https://github.com/CatalaLang/catala/issues)
|
||||
(authors "Denis Merigoux")
|
||||
(maintainers "denis.merigoux@inria.fr")
|
||||
(license Apache2)
|
||||
(maintainers "contact@catala-lang.org")
|
||||
(license Apache-2.0)
|
||||
|
||||
|
||||
(package
|
||||
@ -18,18 +20,21 @@
|
||||
"\| higher-level specification languages for fiscal legislation.
|
||||
)
|
||||
(depends
|
||||
(ocaml (>= 4.07.0))
|
||||
(ocaml (>= 4.08.0))
|
||||
(ANSITerminal (>= 0.8.2))
|
||||
(sedlex (>= 2.1))
|
||||
(menhir (>= 20200211))
|
||||
(menhirLib (>= 20200211))
|
||||
(dune-build-info (>= 2.0.1))
|
||||
(unionFind (>= 20200320))
|
||||
(bindlib (>= 5.0.1))
|
||||
(cmdliner (>= 1.0.4))
|
||||
(re (>= 1.9.0))
|
||||
(dune (and :build ))
|
||||
(zarith (>= 1.10))
|
||||
(zarith_stubs_js (>= 0.14.0))
|
||||
(dune (>= 2.2))
|
||||
(ocamlgraph (>= 1.8.8))
|
||||
(odate (>= 0.6))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(using menhir 2.1)
|
||||
(using menhir 2.1)
|
49
examples/Makefile
Normal file
@ -0,0 +1,49 @@
|
||||
BLACK := $(shell tput -Txterm setaf 0)
|
||||
RED := $(shell tput -Txterm setaf 1)
|
||||
GREEN := $(shell tput -Txterm setaf 2)
|
||||
YELLOW := $(shell tput -Txterm setaf 3)
|
||||
LIGHTPURPLE := $(shell tput -Txterm setaf 4)
|
||||
PURPLE := $(shell tput -Txterm setaf 5)
|
||||
BLUE := $(shell tput -Txterm setaf 6)
|
||||
WHITE := $(shell tput -Txterm setaf 7)
|
||||
|
||||
RESET := $(shell tput -Txterm sgr0)
|
||||
|
||||
################################
|
||||
# Running legislation unit tests
|
||||
################################
|
||||
|
||||
# Usage `make <name_of_example_folder>.<name_of_test_file>.<name_of_test_scope>.run`
|
||||
# This Makefile rule assumes the following directory structure:
|
||||
# foo_example
|
||||
# tests/
|
||||
# foo_test_file1.catala
|
||||
# foo_test_file2.catala
|
||||
# ...
|
||||
# foo_implem.catala
|
||||
# ...
|
||||
%.run: .FORCE
|
||||
@SCOPE="$(word 3,$(subst ., ,$*))" $(MAKE) --no-print-directory -C \
|
||||
$(word 1,$(subst ., ,$*)) tests/$(word 2,$(subst ., ,$*)).run \
|
||||
> /dev/null || { echo "[${RED}FAIL${RESET} ${PURPLE}$@${RESET}]"; exit 1; }
|
||||
@echo "${GREEN}PASS${RESET} ${PURPLE}$@${RESET}"
|
||||
|
||||
TEST_FILES?=$(wildcard */tests/*.catala*)
|
||||
|
||||
TEST_FILES_SCOPES_EN=$(foreach TEST_FILE,$(TEST_FILES),\
|
||||
$(foreach TEST_SCOPE,\
|
||||
$(shell grep -Po "declaration scope [^:]*" $(TEST_FILE) | cut -d " " -f 3), \
|
||||
$(word 1,$(subst /, ,$(TEST_FILE))).$(word 1,$(subst ., ,$(word 3,$(subst /, ,$(TEST_FILE))))).$(TEST_SCOPE).run \
|
||||
) \
|
||||
)
|
||||
|
||||
TEST_FILES_SCOPES_FR=$(foreach TEST_FILE,$(TEST_FILES),\
|
||||
$(foreach TEST_SCOPE,\
|
||||
$(shell grep -Po "déclaration champ d'application [^:]*" $(TEST_FILE) | cut -d " " -f 4), \
|
||||
$(word 1,$(subst /, ,$(TEST_FILE))).$(word 1,$(subst ., ,$(word 3,$(subst /, ,$(TEST_FILE))))).$(TEST_SCOPE).run \
|
||||
) \
|
||||
)
|
||||
|
||||
tests: $(TEST_FILES_SCOPES_EN) $(TEST_FILES_SCOPES_FR)
|
||||
|
||||
.FORCE:
|
@ -7,15 +7,11 @@ LATEXMK=latexmk
|
||||
PYGMENTIZE_FR=../../syntax_highlighting/fr/pygments/pygments/env/bin/pygmentize
|
||||
PYGMENTIZE_EN=../../syntax_highlighting/en/pygments/pygments/env/bin/pygmentize
|
||||
|
||||
CATALA=dune exec --no-print-director ../../src/catala.exe -- --debug --language=$(CATALA_LANG)
|
||||
CATALA=dune exec --no-print-director ../../src/catala.exe -- $(CATALA_OPTS) --language=$(CATALA_LANG)
|
||||
|
||||
LEGIFRANCE_CATALA=dune exec ../../src/legifrance_catala.exe --
|
||||
|
||||
CATALA_EXE=../../_build/default/src/catala.exe
|
||||
LEGIFRANCE_CATALA_EXE=../../_build/default/src/legifrance_catala.exe
|
||||
|
||||
CLIENT_ID?=$(shell cat ../../legifrance_oauth_id.txt)
|
||||
CLIENT_SECRET?=$(shell cat ../../legifrance_oauth_secret.txt)
|
||||
|
||||
ifeq ($(CATALA_LANG),fr)
|
||||
PYGMENTIZE=$(PYGMENTIZE_FR)
|
||||
@ -28,8 +24,15 @@ endif
|
||||
# Targets
|
||||
##########################################
|
||||
|
||||
%.run: %.catala_$(CATALA_LANG) $(CATALA_EXE)
|
||||
@$(CATALA) Makefile $<
|
||||
@$(CATALA) \
|
||||
Interpret \
|
||||
-s $(SCOPE) \
|
||||
$<
|
||||
|
||||
%.tex: %.catala_$(CATALA_LANG) $(CATALA_EXE)
|
||||
$(CATALA) Makefile $<
|
||||
@$(CATALA) Makefile $<
|
||||
$(CATALA) \
|
||||
--wrap \
|
||||
--pygmentize=$(PYGMENTIZE) \
|
||||
@ -37,16 +40,13 @@ endif
|
||||
$<
|
||||
|
||||
%.html: %.catala_$(CATALA_LANG) $(CATALA_EXE)
|
||||
$(CATALA) Makefile $<
|
||||
@$(CATALA) Makefile $<
|
||||
$(CATALA) \
|
||||
--wrap \
|
||||
--pygmentize=$(PYGMENTIZE) \
|
||||
HTML \
|
||||
$<
|
||||
|
||||
%.expired: %.catala_$(CATALA_LANG) $(CATALA_EXE) $(LEGIFRANCE_CATALA_EXE)
|
||||
$(LEGIFRANCE_CATALA) $< $(CLIENT_ID) $(CLIENT_SECRET)
|
||||
|
||||
%.pdf: %.tex
|
||||
cd $(@D) && $(LATEXMK) -g -pdf -halt-on-error -shell-escape $(%F)
|
||||
|
||||
@ -56,7 +56,10 @@ endif
|
||||
|
||||
clean:
|
||||
$(LATEXMK) -f -C $(SRC:.catala_$(CATALA_LANG)=.tex)
|
||||
rm -rf $(SRC:.catala_$(CATALA_LANG)=.tex) $(SRC:.catala_$(CATALA_LANG)=.d) _minted-$(SRC:.catala_$(CATALA_LANG)=)
|
||||
rm -rf $(SRC:.catala_$(CATALA_LANG)=.tex) \
|
||||
$(SRC:.catala_$(CATALA_LANG)=.d) \
|
||||
_minted-$(SRC:.catala_$(CATALA_LANG)=) \
|
||||
$(SRC:.catala_$(CATALA_LANG)=.html)
|
||||
|
||||
include $(wildcard $(SRC:.catala_$(CATALA_LANG)=.d))
|
||||
|
122
examples/README.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Catala examples
|
||||
|
||||
This directory contains examples of Catala programs. It is highly recommended
|
||||
to locate your own Catala programs in this directory, since programs in this
|
||||
directory will receive first-class support during the alpha and beta stage
|
||||
of the Catala programming language development.
|
||||
|
||||
## List of examples
|
||||
|
||||
* `allocations_familiales/`: computation of the French family benefits, based
|
||||
on the _Code de la sécurité sociale_. This case study is the biggest and
|
||||
most ambitious for Catala so far.
|
||||
* `code_general_impots/`: computation of the French income tax, based on the
|
||||
_Code général des impôts_. Currently, there are only stubs of program.
|
||||
* `tutorial/`: Catala language tutorial for developers of tech-savvy lawyers.
|
||||
The tutorial is written like a piece of legislation that gets annotated by
|
||||
Catala snippets.
|
||||
* `us_tax_code/`: contains the Catala formalization of several sections of the
|
||||
US Tax Code.
|
||||
|
||||
## Building and running examples
|
||||
|
||||
Building and running examples is done via Makefiles. Each example directory
|
||||
contains its own Makefile, which includes `Makefile.common.mk`. This common
|
||||
Makefiles defines a list of targets that call the Catala compiler with the
|
||||
right options. Each of these targers can be called from the root of the
|
||||
repository with:
|
||||
|
||||
make -C examples/<directory of example> <name of target>
|
||||
|
||||
The `<name of target>` can be replaced with the following (we assume an example
|
||||
file `examples/foo/foo.catala_en`) list.
|
||||
|
||||
* `foo.tex`: builds the LaTeX literate programming output from the Catala program
|
||||
* `foo.pdf`: compiles `foo.tex` using `latexmk`
|
||||
* `foo.html`: builds the HTML literate programming output from the Catala program
|
||||
* `foo.run`: interprets the Catala program contained in `foo.catala_en`. Note
|
||||
that you have to pass in the scope that you want to interpret via the `SCOPE`
|
||||
Makefile variable (`SCOPE=FooScope make -C examples/foo foo.run`).
|
||||
|
||||
When invoking any of these targets, additional options to the Catala compiler
|
||||
can be passed using the `CATALA_OPTS` Makefile variable.
|
||||
|
||||
## Testing examples
|
||||
|
||||
Unit testing is important, and we encourage Catala developers to write lots
|
||||
of tests for their programs. Again, the Makefile system provides a way to
|
||||
collect tests into a regression test suite.
|
||||
|
||||
In order to enjoy the benefits of this system, you have to create a `tests/`
|
||||
directory in your examples directory, for instance `examples/foo/tests`. Then,
|
||||
create a test file `foo_tests.catala_en` inside that directory.
|
||||
|
||||
Inside `foo_tests.catala_en`, declare one ore more test scopes. It is assumed
|
||||
that all these scopes should execute correctly. Include the program scope you
|
||||
want to test and use assertions to provide the expected values of your test.
|
||||
See existing tests in examples directory for more information.
|
||||
|
||||
Once your tests are written, then will automatically be added to the regression
|
||||
suite executed using
|
||||
|
||||
make -C examples tests
|
||||
|
||||
You can isolate a part of the regression suite by invoking:
|
||||
|
||||
TEST_FILES=examples/tests/foo/foo_tests.catala_en make -C examples tests
|
||||
|
||||
## Adding an example
|
||||
|
||||
This section describes what to do to setup a working directory for a new Catala
|
||||
example, as well as the development cycle. Let us suppose that you want to
|
||||
create a new example named `foo`.
|
||||
|
||||
First, follow the instructions of [the installation readme](../INSTALL.md) to
|
||||
get the compiler up and working up to `make build`. You can also set up the
|
||||
syntax highlighting for your editor.
|
||||
|
||||
Then, create the directory `examples/foo`. In there, create a master source
|
||||
file `foo.catala` that will be the root of your Catala program.
|
||||
You can then start programming in `foo.catala`, or split up your example
|
||||
into multiple files. In the later case, `foo.catala` must only contain
|
||||
something like this:
|
||||
|
||||
```
|
||||
@@Master file@@
|
||||
|
||||
@@Include: bar.catala@@
|
||||
```
|
||||
|
||||
where `examples/bar.catala` is another source file containing code for your
|
||||
example. Make sure you start by including some content in the source files,
|
||||
like
|
||||
|
||||
```
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
To build and run the example, create a `Makefile` in `foo/`
|
||||
with the following contents:
|
||||
|
||||
```Makefile
|
||||
CATALA_LANG=en # or fr if your source code is in French
|
||||
SRC=foo.catala
|
||||
|
||||
include ../Makefile.common.mk
|
||||
```
|
||||
|
||||
The `include` creates automatically all the targets you will need for your example. For instance, after making sure the compiler is built, you can launch
|
||||
|
||||
```
|
||||
make -C examples/foo foo.tex
|
||||
```
|
||||
|
||||
from the repository root to create the LaTeX weaving output of your source
|
||||
program. `Hello, world!` should appear in `examples/foo/foo.tex`.
|
||||
|
||||
Finally, please add a rule for your example in the repository root
|
||||
`Makefile` in the section "Examples-related rules", following the pattern
|
||||
for other examples. This will ensure that your example is built every
|
||||
time the compiler is modified; if a change in the compiler breaks your example,
|
||||
the authors will be notified and find a solution.
|
||||
|
@ -1,4 +1,4 @@
|
||||
CATALA_LANG=fr
|
||||
SRC=allocations_familiales.catala_fr
|
||||
|
||||
include ../Makefile.common
|
||||
include ../Makefile.common.mk
|
||||
|
@ -2,10 +2,11 @@
|
||||
|
||||
@@Inclusion: metadata.catala_fr@@
|
||||
|
||||
@@Inclusion: decrets_divers.catala_fr@@
|
||||
|
||||
@@Inclusion: securite_sociale_L.catala_fr@@
|
||||
|
||||
@@Inclusion: securite_sociale_R.catala_fr@@
|
||||
|
||||
@@Inclusion: securite_sociale_D.catala_fr@@
|
||||
|
||||
@@Inclusion: decrets_divers.catala_fr@@
|
||||
|
@ -10,11 +10,11 @@ Au titre de l’année 2020, l’article 81 de la loi du 24 décembr
|
||||
Le montant précité de la base mensuelle de calcul des allocations familiales (BMAF), en pourcentage duquel sont fxés les montants des prestations familiales, est ainsi porté de 413,16 € à 414,4 € au 1er avril 2020.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition prestations_familiales.base_mensuelle
|
||||
champ d'application PrestationsFamiliales :
|
||||
définition base_mensuelle
|
||||
sous condition
|
||||
date_calcul >= |01/04/2020| et
|
||||
date_calcul < |01/04/2021|
|
||||
date_courante >=@ |01/04/2020| et
|
||||
date_courante <@ |01/04/2021|
|
||||
conséquence égal à 414,4 €
|
||||
*/
|
||||
|
||||
@ -32,79 +32,77 @@ Ils indiquent les montants relatifs aux allocations familiales, à l’
|
||||
|
||||
Je vous demande de bien vouloir transmettre à la connaissance des organismes débiteurs les présentes instructions.
|
||||
|
||||
@@Inclusion: JORFTEXT000000227447@@
|
||||
|
||||
/*
|
||||
# Cependant, le cas de Mayotte n'est pas traité dans la loi et ce sont donc
|
||||
# les règles de cette annexe qui s'apppliquent.
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.base sous condition
|
||||
ménage.résidence = Mayotte et
|
||||
allocations_familiales.droits_ouverts
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants = 1 alors
|
||||
allocations_familiales.montant_premier_enfant
|
||||
sinon (
|
||||
allocations_familiales.montant_deuxieme_enfant +
|
||||
allocations_familiales.montant_troisième_enfant_et_plus
|
||||
)
|
||||
# champ d'application CalculAllocationsFamiliales :
|
||||
# définition allocations_familiales.base sous condition
|
||||
# ménage.résidence = Mayotte et
|
||||
# allocations_familiales.droits_ouverts
|
||||
# conséquence égal à
|
||||
# si nombre de ménage.enfants = 1 alors
|
||||
# allocations_familiales.montant_premier_enfant
|
||||
# sinon (
|
||||
# allocations_familiales.montant_deuxieme_enfant +
|
||||
# allocations_familiales.montant_troisième_enfant_et_plus
|
||||
# )
|
||||
|
||||
définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
ménage.résidence = Mayotte et
|
||||
date_calcul > |30/04/2020| et
|
||||
date_calcul <= |31/12/2020|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 30,68 %
|
||||
# définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
# ménage.résidence = Mayotte et
|
||||
# date_calcul > |30/04/2020| et
|
||||
# date_calcul <= |31/12/2020|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 30,68 %
|
||||
|
||||
définition allocations_familiales.montant_troisième_enfant_et_plus sous condition
|
||||
ménage.résidence = Mayotte et
|
||||
date_calcul > |30/04/2020| et
|
||||
date_calcul <= |31/12/2020|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 14,3 % +
|
||||
(si nombre de ménage.enfants >= 4 alors
|
||||
prestations_familiales.base_mensuelle * 4,63 % *
|
||||
(nombre de ménage.enfants - 3)
|
||||
sinon 0 €)
|
||||
# définition allocations_familiales.montant_troisième_enfant_et_plus sous condition
|
||||
# ménage.résidence = Mayotte et
|
||||
# date_calcul > |30/04/2020| et
|
||||
# date_calcul <= |31/12/2020|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 14,3 % +
|
||||
# (si nombre de ménage.enfants >= 4 alors
|
||||
# prestations_familiales.base_mensuelle * 4,63 % *
|
||||
# (nombre de ménage.enfants - 3)
|
||||
# sinon 0 €)
|
||||
|
||||
définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
ménage.résidence = Mayotte et
|
||||
date_calcul > |01/01/2021| et
|
||||
date_calcul <= |31/03/2021|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 32 %
|
||||
# définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
# ménage.résidence = Mayotte et
|
||||
# date_calcul > |01/01/2021| et
|
||||
# date_calcul <= |31/03/2021|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 32 %
|
||||
|
||||
définition allocations_familiales.montant_troisième_enfant_et_plus sous condition
|
||||
ménage.résidence = Mayotte et
|
||||
date_calcul > |01/01/2021| et
|
||||
date_calcul <= |31/03/2021|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 16 % +
|
||||
(si nombre de ménage.enfants >= 4 alors
|
||||
prestations_familiales.base_mensuelle * 4,63 % *
|
||||
(nombre de ménage.enfants - 3)
|
||||
sinon 0 €)
|
||||
# définition allocations_familiales.montant_troisième_enfant_et_plus sous condition
|
||||
# ménage.résidence = Mayotte et
|
||||
# date_calcul > |01/01/2021| et
|
||||
# date_calcul <= |31/03/2021|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 16 % +
|
||||
# (si nombre de ménage.enfants >= 4 alors
|
||||
# prestations_familiales.base_mensuelle * 4,63 % *
|
||||
# (nombre de ménage.enfants - 3)
|
||||
# sinon 0 €)
|
||||
|
||||
définition allocations_familiales.montant_premier_enfant sous condition
|
||||
ménage.résident = Mayotte et
|
||||
allocations_familiales.date_ouverture_droits < |01/01/2012|
|
||||
conséquence égal à 57,28 €
|
||||
# définition allocations_familiales.montant_premier_enfant sous condition
|
||||
# ménage.résident = Mayotte et
|
||||
# allocations_familiales.date_ouverture_droits < |01/01/2012|
|
||||
# conséquence égal à 57,28 €
|
||||
|
||||
définition allocations_familiales.montant_premier_enfant sous condition
|
||||
ménage.résident = Mayotte et
|
||||
allocations_familiales.date_ouverture_droits >= |01/01/2012| et
|
||||
date_calcul > |30/04/2020| et
|
||||
date_calcul <= |31/12/2020|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 7,17 %
|
||||
# définition allocations_familiales.montant_premier_enfant sous condition
|
||||
# ménage.résident = Mayotte et
|
||||
# allocations_familiales.date_ouverture_droits >= |01/01/2012| et
|
||||
# date_calcul > |30/04/2020| et
|
||||
# date_calcul <= |31/12/2020|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 7,17 %
|
||||
|
||||
définition allocations_familiales.montant_premier_enfant sous condition
|
||||
ménage.résident = Mayotte et
|
||||
allocations_familiales.date_ouverture_droits >= |01/01/2012| et
|
||||
date_calcul > |01/01/2021| et
|
||||
date_calcul <= |31/03/2021|
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 5,88 %
|
||||
# définition allocations_familiales.montant_premier_enfant sous condition
|
||||
# ménage.résident = Mayotte et
|
||||
# allocations_familiales.date_ouverture_droits >= |01/01/2012| et
|
||||
# date_calcul > |01/01/2021| et
|
||||
# date_calcul <= |31/03/2021|
|
||||
# conséquence égal à
|
||||
# prestations_familiales.base_mensuelle * 5,88 %
|
||||
*/
|
||||
|
||||
|
||||
@ -114,21 +112,21 @@ champ d'application CalculAllocationsFamiliales :
|
||||
A compter du 1er janvier 2020, pour les catégories de travailleurs mentionnés à l' article L. 2211-1 du code du travail , le montant du salaire minimum de croissance est relevé dans les conditions ci-après :
|
||||
1° En métropole, en Guadeloupe, en Guyane, en Martinique, à La Réunion, à Saint-Barthélemy, à Saint-Martin et à Saint-Pierre-et-Miquelon, son montant est porté à 10,15 € l'heure ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition smic.brut_horaire sous condition
|
||||
(ménage.résidence = Métropole) ou
|
||||
(ménage.résidence = Guyane) ou
|
||||
(ménage.résidence = Martinique) ou
|
||||
(ménage.résidence = LaRéunion) ou
|
||||
(ménage.résidence = SaintBarthélemy) ou
|
||||
(ménage.résidence = SaintMartin) ou
|
||||
(ménage.résidence = SaintPierreEtMiquelon)
|
||||
champ d'application Smic :
|
||||
définition brut_horaire sous condition
|
||||
(résidence = Métropole) ou
|
||||
(résidence = Guyane) ou
|
||||
(résidence = Martinique) ou
|
||||
(résidence = LaRéunion) ou
|
||||
(résidence = SaintBarthélemy) ou
|
||||
(résidence = SaintMartin) ou
|
||||
(résidence = SaintPierreEtMiquelon)
|
||||
conséquence égal à 10,15 €
|
||||
*/
|
||||
2° A Mayotte, son montant est fixé à 7,66 € l'heure.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition smic.brut_horaire sous condition
|
||||
(ménage.résidence = Mayotte)
|
||||
champ d'application Smic :
|
||||
définition brut_horaire sous condition
|
||||
(résidence = Mayotte)
|
||||
conséquence égal à 7,66 €
|
||||
*/
|
||||
|
@ -1,25 +1,26 @@
|
||||
@@Début métadonnées@@
|
||||
/*
|
||||
déclaration structure Personne :
|
||||
donnée prénom contenu texte
|
||||
donnée nom contenu texte
|
||||
donnée date_de_naissance contenu date
|
||||
déclaration structure Personne:
|
||||
donnée numéro_sécurité_sociale contenu entier
|
||||
|
||||
déclaration structure ParentsGardeAlternée :
|
||||
donnée parent1 contenu Personne
|
||||
donnée parent2 contenu Personne
|
||||
déclaration énumération GardeAlternée :
|
||||
-- OuiPartageAllocations
|
||||
-- OuiAllocataireUnique
|
||||
-- NonGardeUnique
|
||||
|
||||
déclaration énumération PriseEnCharge :
|
||||
-- Complète contenu Personne
|
||||
-- GardeAlternée contenu ParentsGardeAlternée
|
||||
déclaration énumération PriseEnChargeServiceSociaux:
|
||||
-- OuiAllocationVerséeÀLaFamille
|
||||
-- OuiAllocationVerséeAuxServicesSociaux
|
||||
-- NonPriseEnChargeFamille
|
||||
|
||||
# Une structure regroupe des données ensemble
|
||||
déclaration structure Enfant :
|
||||
donnée fin_obligation_scolaire contenu date
|
||||
donnée rémuneration_mensuelle contenu argent
|
||||
donnée date_de_naissance contenu date
|
||||
donnée âge contenu entier
|
||||
donnée rémuneration_mensuelle contenu montant
|
||||
donnée prise_en_charge contenu PriseEnCharge
|
||||
condition confié_service_social
|
||||
donnée garde_alternée contenu GardeAlternée
|
||||
donnée pris_en_charge_par_services_sociaux contenu
|
||||
PriseEnChargeServiceSociaux
|
||||
|
||||
déclaration énumération Collectivité :
|
||||
-- Guadeloupe
|
||||
@ -32,198 +33,147 @@ déclaration énumération Collectivité :
|
||||
-- SaintPierreEtMiquelon
|
||||
-- Mayotte
|
||||
|
||||
déclaration structure Ménage :
|
||||
donnée enfants contenu collection Enfant
|
||||
donnée parent_en_charge contenu Personne dépend de Enfant
|
||||
donnée enfant_plus_âgé contenu optionnel Enfant
|
||||
donnée parents contenu collection Personne
|
||||
donnée parent1 contenu Personne
|
||||
donnée parent2 contenu optionnel Personne
|
||||
donnée résidence contenu Collectivité
|
||||
# Le mot optionnel permet de prévoir le cas où le
|
||||
# ménage n'a pas d'enfants
|
||||
déclaration champ d'application Smic :
|
||||
contexte résidence contenu Collectivité
|
||||
contexte brut_horaire contenu argent
|
||||
|
||||
déclaration champ d'application MénageBienFormé :
|
||||
contexte ménage contenu Ménage
|
||||
|
||||
champ d'application MénageBienFormé :
|
||||
assertion nombre de parents > 0 et nombre de parents <= 2
|
||||
|
||||
# Les champs parent1 et parent2 sont cohérents
|
||||
assertion ménage.parent1 dans ménage.parents
|
||||
assertion (
|
||||
selon ménage.parent2 sous forme
|
||||
-- Présent de parent2 :
|
||||
parent2 dans ménage.parents et parent2 != ménage.parent1
|
||||
-- Absent : vrai
|
||||
)
|
||||
|
||||
# Dans la traduction informatique, le ménage est en charge de chacun
|
||||
# des enfants
|
||||
assertion (pour tout enfant dans enfants on a
|
||||
selon enfant.prise_en_charge sous forme
|
||||
-- Complète de parent : parent dans ménage.parents
|
||||
-- GardeAlternée de parents_garde_alternée :
|
||||
parents_garde_alternée.parent1 dans ménage.parents ou
|
||||
parents_garde_alternée.parent2 dans ménage.parents
|
||||
)
|
||||
|
||||
# parent_en_charge permet de savoir quel parent du ménage
|
||||
# est en charge de quel enfant
|
||||
définition parent_en_charge de enfant égal à (
|
||||
selon enfant.prise_en_charge sous forme
|
||||
-- Complète de parent : parent
|
||||
-- GardeAlternée de parents_garde_alternée : (
|
||||
si parents_garde_alternée.parent1 dans ménage.parents
|
||||
alors parents_garde_alternée.parent1
|
||||
sinon parents_garde_alternée.parent2
|
||||
))
|
||||
|
||||
# Lorsqu'un enfant est confié au service social, il ne peut être
|
||||
# en garde alternée
|
||||
assertion pour tout enfant dans enfants on a
|
||||
si enfant.confié_service_social
|
||||
alors enfant.prise_en_charge = Complète contenu service_social
|
||||
sinon vrai
|
||||
|
||||
déclaration énumération Prestation:
|
||||
-- PrestationAccueilJeuneEnfant
|
||||
-- AllocationFamiliale
|
||||
-- ComplémentFamilial
|
||||
-- AllocationLogement
|
||||
-- AllocationÉducationEnfantHandicapé
|
||||
-- AllocationSoutienFamilial
|
||||
-- AllocationRentréeScolaire
|
||||
-- AllocationJournalièrePresenceParentale
|
||||
|
||||
déclaration structure PrestationsFamiliales :
|
||||
condition conditions_hors_âge dépend de Enfant
|
||||
condition droits_ouverts dépend de Enfant
|
||||
donnée base_mensuelle contenu montant
|
||||
donnée base_mensuelle_dom contenu montant
|
||||
|
||||
déclaration énumération ChargeAllocation :
|
||||
déclaration énumération PriseEnCompteÉvaluationMontant:
|
||||
-- Complète
|
||||
-- Partagée
|
||||
|
||||
déclaration structure AllocationsFamiliales :
|
||||
condition droits_ouverts
|
||||
donnée date_ouverture_droits contenu date
|
||||
condition conditions_hors_âge dépend de Enfant
|
||||
donnée base contenu montant
|
||||
donnée avec_garde_alternée contenu montant
|
||||
donnée montant_versé contenu montant
|
||||
donnée récipiendaire_par_enfant contenu Personne dépend de Enfant
|
||||
donnée charge_par_enfant contenu ChargeAllocation dépend de Enfant
|
||||
donnée rapport_enfants_total_moyen contenu décimal
|
||||
donnée nombre_total_enfants contenu entier
|
||||
donnée nombre_moyen_enfants contenu décimal
|
||||
donnée montant_premier_enfant contenu montant
|
||||
donnée montant_deuxieme_enfant contenu montant
|
||||
donnée montant_troisième_enfant_et_plus contenu montant
|
||||
déclaration énumération VersementAllocations:
|
||||
-- Normal
|
||||
-- AllocationVerséeAuxServicesSociaux
|
||||
|
||||
déclaration structure AllocationForfaitaire :
|
||||
condition droits_ouverts dépend de Enfant
|
||||
donnée montant_versé contenu montant
|
||||
déclaration énumération ÂgeAlternatif:
|
||||
-- Absent
|
||||
-- Présent contenu entier
|
||||
|
||||
déclaration énumération ChoixParentAllocataire :
|
||||
-- UnParent contenu Personne
|
||||
-- DeuxParents
|
||||
déclaration énumération ÉlémentPrestationsFamiliales:
|
||||
-- PrestationAccueilJeuneEnfant
|
||||
-- AllocationsFamiliales
|
||||
-- ComplémentFamilial
|
||||
-- AllocationLogement
|
||||
-- AllocationÉducationEnfantHandicapé
|
||||
-- AllocationSoutienFamilial
|
||||
-- AllocationRentréeScolaire
|
||||
-- AllocationJournalièrePresenceParentale
|
||||
|
||||
déclaration structure AllocationsGardeAlternée :
|
||||
donnée choix_allocataire contenu optionnel ChoixParentAllocataire
|
||||
dépend de Enfant
|
||||
condition unique_allocataire dépend de Enfant
|
||||
condition allocataire_double dépend de Enfant
|
||||
condition demande_conjointe_partage_charge dépend de Enfant
|
||||
condition desaccord_charge dépend de Enfant
|
||||
déclaration champ d'application PrestationsFamiliales:
|
||||
contexte droit_ouvert condition dépend de Enfant
|
||||
contexte conditions_hors_âge condition dépend de Enfant
|
||||
contexte plafond_l512_3_2 contenu argent
|
||||
contexte âge_l512_3_2 contenu entier
|
||||
contexte âge_l512_3_2_alternatif contenu ÂgeAlternatif
|
||||
contexte régime_outre_mer_l751_1 condition
|
||||
contexte date_courante contenu date
|
||||
contexte prestation_courante contenu ÉlémentPrestationsFamiliales
|
||||
contexte résidence contenu Collectivité
|
||||
contexte smic champ d'application Smic
|
||||
contexte base_mensuelle contenu argent
|
||||
|
||||
déclaration structure MajorationsAllocationsFamiliales :
|
||||
condition droits_ouverts dépend de Enfant
|
||||
donnée base_par_enfant contenu montant dépend de Enfant
|
||||
donnée avec_garde_alternée contenu montant dépend de Enfant
|
||||
donnée montant_versé contenu montant
|
||||
champ d'application PrestationsFamiliales:
|
||||
définition smic.résidence égal à résidence
|
||||
|
||||
déclaration structure ComplémentDégressif :
|
||||
condition droits_ouverts dépend de montant
|
||||
donnée dépassement contenu montant dépend de montant
|
||||
donnée pour_allocation_forfaitaire contenu montant
|
||||
donnée pour_allocations_familiales_et_majorations contenu montant
|
||||
donnée montant_versé contenu montant
|
||||
déclaration champ d'application AllocationFamilialesAvril2008:
|
||||
contexte âge_limite_alinéa_1_l521_3 contenu entier
|
||||
|
||||
déclaration structure TitreI:
|
||||
condition droits_ouverts_allocations_familiales dépend de Personne
|
||||
déclaration énumération StockageEnfant:
|
||||
-- PasEnfant
|
||||
-- UnEnfant contenu Enfant
|
||||
|
||||
déclaration structure L511_1:
|
||||
donnée prestation_courante contenu Prestation
|
||||
déclaration champ d'application EnfantLePlusÂgé:
|
||||
contexte enfants contenu collection Enfant
|
||||
contexte est_le_plus_âgé contenu booléen dépend de Enfant
|
||||
|
||||
déclaration structure L512_3 :
|
||||
donnée âge_limite_alinéa_2 contenu entier
|
||||
donnée âge_limite_alinéa_2_alternatif contenu entier
|
||||
condition âge_limite_alinéa_2_alternatif_utilisé
|
||||
donnée plafond_rémunération_mensuelle_alinéa_2 contenu montant
|
||||
champ d'application EnfantLePlusÂgé:
|
||||
définition est_le_plus_âgé de enfant égal à
|
||||
faux # TODO: changer!
|
||||
|
||||
déclaration structure L521_1 :
|
||||
donnée nombre_minimum_enfants contenu montant
|
||||
donnée ressources_ménage contenu montant
|
||||
déclaration champ d'application AllocationsFamiliales:
|
||||
contexte enfants_à_charge contenu collection Enfant
|
||||
contexte date_courante contenu date
|
||||
contexte résidence contenu Collectivité
|
||||
contexte ressources_ménage contenu argent
|
||||
contexte prise_en_compte contenu
|
||||
PriseEnCompteÉvaluationMontant dépend de Enfant
|
||||
contexte versement contenu
|
||||
VersementAllocations dépend de Enfant
|
||||
|
||||
déclaration structure L521_3 :
|
||||
donnée âge_limite_alinéa_1 contenu entier dépend de Enfant
|
||||
donnée minimum_alinéa_2 contenu montant
|
||||
contexte montant_versé contenu argent
|
||||
|
||||
déclaration structure L751_1 :
|
||||
condition régime_outre_mer
|
||||
contexte droit_ouvert_base condition
|
||||
contexte droit_ouvert_majoration condition dépend de Enfant
|
||||
contexte montant_versé_base contenu argent
|
||||
contexte montant_avec_garde_alternée_base contenu argent
|
||||
contexte montant_initial_base contenu argent
|
||||
contexte montant_initial_base_premier_enfant contenu argent
|
||||
contexte montant_initial_base_deuxième_enfant contenu argent
|
||||
contexte montant_initial_base_troisième_enfant_et_plus contenu argent
|
||||
contexte rapport_enfants_total_moyen contenu décimal
|
||||
contexte nombre_moyen_enfants contenu décimal
|
||||
contexte nombre_total_enfants contenu décimal
|
||||
|
||||
déclaration structure D521_3 :
|
||||
donnée plafond_I contenu montant
|
||||
donnée plafond_II contenu montant
|
||||
contexte droit_ouvert_forfaitaire condition dépend de Enfant
|
||||
contexte montant_versé_forfaitaire contenu argent
|
||||
|
||||
déclaration structure SMIC :
|
||||
donnée brut_horaire contenu montant dépend de Collectivité
|
||||
contexte montant_versé_majoration contenu argent
|
||||
contexte montant_avec_garde_alternée_majoration contenu argent dépend de Enfant
|
||||
contexte montant_initial_majoration contenu argent dépend de Enfant
|
||||
|
||||
déclaration champ d'application CalculPrestationsFamiliales :
|
||||
# Les règles déclarées dans PrestationsFamiliales pourront utiliser
|
||||
# ménage et l512_3 et leur données associées
|
||||
contexte date_calcul contenu date
|
||||
contexte ménage contenu Ménage
|
||||
contexte prestations_familiales contenu PrestationsFamiliales
|
||||
contexte l511_1 contenu L511_1
|
||||
contexte l512_3 contenu L512_3
|
||||
contexte ménage_bien_formé champ d'application MénageBienFormé
|
||||
contexte droit_ouvert_complément condition
|
||||
contexte montant_versé_complément_pour_base_et_majoration contenu argent
|
||||
contexte montant_base_complément_pour_base_et_majoration contenu argent
|
||||
contexte montant_versé_complément_pour_forfaitaire contenu argent
|
||||
contexte dépassement_plafond_ressources contenu argent dépend de argent
|
||||
|
||||
champ d'application CalculPrestationsFamiliales:
|
||||
définition ménage_bien_formé.ménage égal à ménage
|
||||
contexte prestations_familiales champ d'application PrestationsFamiliales
|
||||
contexte version_avril_2008 champ d'application AllocationFamilialesAvril2008
|
||||
contexte enfant_le_plus_âgé champ d'application EnfantLePlusÂgé
|
||||
|
||||
déclaration champ d'application CalculAllocationsFamilialesAvril2008 :
|
||||
contexte l521_3 contenu L521_3
|
||||
contexte conditions_hors_âge condition dépend de Enfant
|
||||
contexte nombre_enfants_l521_1 contenu entier
|
||||
contexte âge_limite_alinéa_1_l521_3 contenu entier dépend de Enfant
|
||||
contexte nombre_enfants_alinéa_2_l521_3 contenu entier
|
||||
contexte est_enfant_le_plus_âgé contenu booléen dépend de Enfant
|
||||
contexte plafond_I_d521_3 contenu argent
|
||||
contexte plafond_II_d521_3 contenu argent
|
||||
|
||||
déclaration champ d'application CalculAllocationsFamiliales :
|
||||
contexte date_calcul contenu date
|
||||
contexte ménage contenu Ménage
|
||||
contexte allocations_familiales contenu AllocationsFamiliales
|
||||
contexte allocation_forfaitaire contenu AllocationForfaitaire
|
||||
contexte allocations_garde_alternée contenu AllocationsGardeAlternée
|
||||
contexte majorations_allocations_familiales
|
||||
contenu MajorationsAllocationsFamiliales
|
||||
contexte smic contenu SMIC
|
||||
|
||||
contexte titre_I contenu TitreI
|
||||
contexte l521_1 contenu L521_1
|
||||
contexte l521_3 contenu L521_3
|
||||
contexte l751_1 contenu L751_1
|
||||
contexte d521_1 contenu D521_1
|
||||
contexte d521_3 contenu D521_3
|
||||
contexte calcul_prestations_familiales
|
||||
champ d'application CalculPrestationsFamiliales
|
||||
contexte calcul_avril_2008
|
||||
champ d'application CalculAllocationsFamilialesAvril2008
|
||||
## Code non défini dans la loi
|
||||
champ d'application PrestationsFamiliales:
|
||||
définition âge_l512_3_2_alternatif égal à Absent
|
||||
|
||||
champ d'application CalculAllocationsFamiliales:
|
||||
définition calcul_prestations_familiales.ménage égal à ménage
|
||||
définition calcul_prestations_familiales.date_calcul égal à date_calcul
|
||||
# AllocationsFamiliales est un cas particulier de PrestationsFamiliales,
|
||||
# le dernier est donc inclus dans l'autre. Il est nécessaire de préciser
|
||||
# que les deux contextes parlent du même ménage pour caractériser
|
||||
# l'inclusion.
|
||||
champ d'application AllocationsFamiliales:
|
||||
définition prestations_familiales.prestation_courante égal à
|
||||
AllocationsFamiliales
|
||||
définition prestations_familiales.date_courante égal à
|
||||
date_courante
|
||||
définition prestations_familiales.résidence égal à
|
||||
résidence
|
||||
|
||||
assertion (pour tout enfant dans enfants_à_charge on a
|
||||
prestations_familiales.droit_ouvert de enfant)
|
||||
|
||||
définition enfant_le_plus_âgé.enfants égal à enfants_à_charge
|
||||
définition est_enfant_le_plus_âgé de enfant égal à
|
||||
enfant_le_plus_âgé.est_le_plus_âgé de enfant
|
||||
|
||||
définition montant_versé_base égal à
|
||||
si droit_ouvert_base alors montant_avec_garde_alternée_base sinon 0€
|
||||
définition montant_versé_majoration égal à
|
||||
si droit_ouvert_base alors
|
||||
somme argent pour enfant dans enfants_à_charge de
|
||||
montant_avec_garde_alternée_majoration de enfant
|
||||
sinon 0€
|
||||
|
||||
définition montant_versé égal à
|
||||
si droit_ouvert_base alors
|
||||
montant_versé_base +€
|
||||
montant_versé_majoration +€
|
||||
montant_versé_forfaitaire +€
|
||||
montant_versé_complément_pour_base_et_majoration +€
|
||||
montant_versé_complément_pour_forfaitaire
|
||||
sinon 0€
|
||||
*/
|
||||
@@Fin métadonnées@@
|
||||
|
@ -8,237 +8,267 @@
|
||||
@@Chapitre 1er : Allocations familiales@@++++
|
||||
|
||||
@Article D521-1|LEGIARTI000030680318@
|
||||
I.-Pour l'application de l'article L. 521-1 , le montant des allocations familiales et de la majoration pour âge prévue à l'article L. 521-3 est défini selon le barème suivant :
|
||||
I.-Pour l'application de l'article L. 521-1 , le montant des allocations
|
||||
familiales et de la majoration pour âge prévue à l'article L. 521-3
|
||||
est défini selon le barème suivant :
|
||||
/*
|
||||
# Composantes des allocations familiales
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.base sous condition
|
||||
allocations_familiales.droits_ouverts
|
||||
conséquence égal à
|
||||
allocations_familiales.montant_deuxieme_enfant +
|
||||
allocations_familiales.montant_troisième_enfant_et_plus
|
||||
champ d'application AllocationsFamiliales :
|
||||
étiquette définition_montant_initial_base
|
||||
définition montant_initial_base égal à
|
||||
montant_initial_base_deuxième_enfant +€
|
||||
montant_initial_base_troisième_enfant_et_plus
|
||||
*/
|
||||
1° Lorsque le ménage ou la personne a disposé d'un montant de ressources inférieur ou égal au plafond défini au I de l'article D. 521-3, les taux servant au calcul des allocations familiales sont fixés, en pourcentage de la base mensuelle prévue à l'article L. 551-1, à :
|
||||
1° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
inférieur ou égal au plafond défini au I de l'article D. 521-3, les taux
|
||||
servant au calcul des allocations familiales sont fixés, en pourcentage
|
||||
de la base mensuelle prévue à l'article L. 551-1, à :
|
||||
a) 32 % pour le deuxième enfant à charge ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
l521_1.ressources_ménage <= d521_3.plafond_I
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_deuxième_enfant sous condition
|
||||
ressources_ménage <=€ plafond_I_d521_3
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 2
|
||||
alors prestations_familiales.base_mensuelle * 32 %
|
||||
si nombre de enfants_à_charge >= 2
|
||||
alors prestations_familiales.base_mensuelle *€ 32 %
|
||||
sinon 0 €
|
||||
*/
|
||||
|
||||
b) 41 % pour le troisième enfant à charge et chacun des suivants.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_troisième_enfant_et_plus
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_troisième_enfant_et_plus
|
||||
sous condition
|
||||
l521_1.ressources_ménage <= d521_3.plafond_I
|
||||
ressources_ménage <=€ plafond_I_d521_3
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 3
|
||||
alors prestations_familiales.base_mensuelle * 41 % * (
|
||||
(nombre de ménage.enfants) - 2
|
||||
si nombre de enfants_à_charge >= 3
|
||||
alors (prestations_familiales.base_mensuelle *€ 41 %) *€ (
|
||||
entier_vers_décimal de ((nombre de enfants_à_charge) - 2)
|
||||
) sinon 0 €
|
||||
*/
|
||||
La majoration pour âge est fixée à 16 % de la base mensuelle de calcul des prestations familiales ;
|
||||
La majoration pour âge est fixée à 16 % de la base mensuelle de calcul des
|
||||
prestations familiales ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition
|
||||
majorations_allocations_familiales.base_par_enfant de enfant
|
||||
sous condition
|
||||
l521_1.ressources_ménage <= d521_3.plafond_I
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_majoration de enfant
|
||||
sous condition
|
||||
ressources_ménage <=€ plafond_I_d521_3 et
|
||||
(nombre de enfants_à_charge >= 2)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 16 %
|
||||
prestations_familiales.base_mensuelle *€ 16 %
|
||||
*/
|
||||
2° Lorsque le ménage ou la personne a disposé d'un montant de ressources supérieur au plafond défini au I de l'article D. 521-3 et inférieur ou égal à celui défini au II du même article, les taux servant au calcul des allocations familiales sont fixés, en pourcentage de la base mensuelle prévue à l'article L. 551-1, à :
|
||||
2° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
supérieur au plafond défini au I de l'article D. 521-3 et inférieur ou égal
|
||||
à celui défini au II du même article, les taux servant au calcul des
|
||||
allocations familiales sont fixés, en pourcentage de la base mensuelle
|
||||
prévue à l'article L. 551-1, à :
|
||||
a) 16 % pour le deuxième enfant à charge ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_I) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_II)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_deuxième_enfant sous condition
|
||||
(ressources_ménage >€ plafond_I_d521_3) et
|
||||
(ressources_ménage <=€ plafond_II_d521_3)
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 2
|
||||
alors prestations_familiales.base_mensuelles * 32 %
|
||||
si nombre de enfants_à_charge >= 2
|
||||
alors prestations_familiales.base_mensuelle *€ 32 %
|
||||
sinon 0 €
|
||||
*/
|
||||
b) 20,5 % pour le troisième enfant à charge et chacun des suivants.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_troisième_enfant_et_plus
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_troisième_enfant_et_plus
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_I) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_II)
|
||||
(ressources_ménage >€ plafond_I_d521_3) et
|
||||
(ressources_ménage <=€ plafond_II_d521_3)
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 3
|
||||
alors prestations_familiales.base_mensuelle * 20,5 % * (
|
||||
(nombre de ménage.enfants) - 2
|
||||
si nombre de enfants_à_charge >= 3
|
||||
alors (prestations_familiales.base_mensuelle *€ 20,5 %) *€ (
|
||||
entier_vers_décimal de ((nombre de enfants_à_charge) - 2)
|
||||
) sinon 0 €
|
||||
*/
|
||||
La majoration pour âge est fixée à 8 % de la base mensuelle de calcul des prestations familiales ;
|
||||
La majoration pour âge est fixée à 8 % de la base mensuelle de calcul des
|
||||
prestations familiales ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition
|
||||
majorations_allocations_familiales.base_par_enfant de enfant
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_I) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_II)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_majoration de enfant sous condition
|
||||
(ressources_ménage >€ plafond_I_d521_3) et
|
||||
(ressources_ménage <=€ plafond_II_d521_3) et
|
||||
(nombre de enfants_à_charge >= 2)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 8 %
|
||||
prestations_familiales.base_mensuelle *€ 8 %
|
||||
*/
|
||||
|
||||
3° Lorsque le ménage ou la personne a disposé d'un montant de ressources supérieur au plafond défini au II de l'article D. 521-3, les taux servant au calcul des allocations familiales sont fixés, en pourcentage de la base mensuelle prévue à l'article L. 551-1, à :
|
||||
3° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
supérieur au plafond défini au II de l'article D. 521-3, les taux servant
|
||||
au calcul des allocations familiales sont fixés, en pourcentage de la base
|
||||
mensuelle prévue à l'article L. 551-1, à :
|
||||
a) 8 % pour le deuxième enfant à charge ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_deuxieme_enfant sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_II)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_deuxième_enfant sous condition
|
||||
(ressources_ménage >€ plafond_II_d521_3)
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 2
|
||||
alors prestations_familiales.base_mensuelle * 8 %
|
||||
si nombre de enfants_à_charge >= 2
|
||||
alors prestations_familiales.base_mensuelle *€ 8 %
|
||||
sinon 0 €
|
||||
*/
|
||||
b) 10,25 % pour le troisième enfant à charge et chacun des suivants.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.montant_troisième_enfant_et_plus
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_base_troisième_enfant_et_plus
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_II)
|
||||
(ressources_ménage >€ plafond_II_d521_3)
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants >= 3
|
||||
alors prestations_familiales.base_mensuelle * 10,25 % * (
|
||||
(nombre de ménage.enfants) - 2
|
||||
si nombre de enfants_à_charge >= 3
|
||||
alors (prestations_familiales.base_mensuelle *€ 10,25 %) *€ (
|
||||
entier_vers_décimal de ((nombre de enfants_à_charge) - 2)
|
||||
) sinon 0 €
|
||||
*/
|
||||
La majoration pour âge est fixée à 4 % de la base mensuelle de calcul des prestations familiales.
|
||||
La majoration pour âge est fixée à 4 % de la base mensuelle de calcul des
|
||||
prestations familiales.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition
|
||||
majorations_allocations_familiales.base_par_enfant de enfant
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_II)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_initial_majoration de enfant sous condition
|
||||
(ressources_ménage >€ plafond_II_d521_3) et
|
||||
(nombre de enfants_à_charge >= 2)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 4 %
|
||||
prestations_familiales.base_mensuelle *€ 4 %
|
||||
*/
|
||||
|
||||
II.-En application du sixième alinéa de l'article L. 521-1, le montant mensuel des allocations familiales et, le cas échéant, de la majoration pour âge est majoré d'un complément dégressif, lorsque les ressources annuelles du ménage ou de la personne dépassent l'un des plafonds défini au I ou au II de l'article D. 521-3 d'une somme inférieure à douze fois le montant mensuel des allocations familiales augmenté, le cas échéant, de la ou des majorations pour âge.
|
||||
II.-En application du sixième alinéa de l'article L. 521-1, le montant
|
||||
mensuel des allocations familiales et, le cas échéant, de la majoration
|
||||
pour âge est majoré d'un complément dégressif, lorsque les ressources
|
||||
annuelles du ménage ou de la personne dépassent l'un des plafonds défini
|
||||
au I ou au II de l'article D. 521-3 d'une somme inférieure à douze fois
|
||||
le montant mensuel des allocations familiales augmenté, le cas échéant,
|
||||
de la ou des majorations pour âge.
|
||||
|
||||
Ce complément dégressif est égal, pour chaque mois, au douzième de la différence entre, d'une part, ce plafond de ressources majoré de la somme définie à l'alinéa précédent et, d'autre part, le montant des ressources.
|
||||
Ce complément dégressif est égal, pour chaque mois, au douzième de la
|
||||
différence entre, d'une part, ce plafond de ressources majoré de la somme
|
||||
définie à l'alinéa précédent et, d'autre part, le montant des ressources.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition complément_dégressif.dépassement de allocation
|
||||
champ d'application AllocationsFamiliales :
|
||||
exception base_dépassement
|
||||
définition dépassement_plafond_ressources de allocation
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_I) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_I + 12 * allocation)
|
||||
(ressources_ménage >€ plafond_I_d521_3) et
|
||||
(ressources_ménage <=€ plafond_I_d521_3 +€ allocation *€ 12,0 )
|
||||
conséquence égal à
|
||||
d521_3.plafond_I + 12 * allocation - l521_1.ressources_ménage
|
||||
plafond_I_d521_3 +€ allocation *€ 12,0 -€ ressources_ménage
|
||||
|
||||
définition complément_dégressif.dépassement de allocation
|
||||
exception base_dépassement
|
||||
définition dépassement_plafond_ressources de allocation
|
||||
sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_II) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_II + 12 * allocation)
|
||||
(ressources_ménage >€ plafond_II_d521_3) et
|
||||
(ressources_ménage <=€ plafond_II_d521_3 +€ allocation *€ 12,0)
|
||||
conséquence égal à
|
||||
d521_3.plafond_II + 12 * allocation - l521_1.ressources_ménage
|
||||
plafond_II_d521_3 +€ allocation *€ 12,0 -€ ressources_ménage
|
||||
|
||||
# Dans les autres cas, le dépassement est nul
|
||||
définition complément_dégressif.dépassement de allocations égal à 0 €
|
||||
étiquette base_dépassement
|
||||
définition dépassement_plafond_ressources de allocations égal à 0 €
|
||||
|
||||
règle complément_dégressif.droits_ouverts de allocation
|
||||
sous condition complément_dégressif.dépassement de allocation > 0 €
|
||||
conséquence rempli
|
||||
définition montant_versé_complément_pour_base_et_majoration égal à
|
||||
(dépassement_plafond_ressources de
|
||||
montant_base_complément_pour_base_et_majoration) *€ (1,0 /. 12,0)
|
||||
|
||||
définition
|
||||
complément_dégressif.pour_allocations_familiales_et_majorations
|
||||
sous condition complément_dégressif.droits_ouverts de complément_dégressif.base
|
||||
conséquence égal à
|
||||
(complément_dégressif.dépassement de complément_dégressif.base) / 12
|
||||
|
||||
définition complément_dégressif.base
|
||||
égal à
|
||||
(si allocations_familiales.droits_ouverts alors
|
||||
allocations_familiales.montant_versé
|
||||
sinon 0 €) +
|
||||
(si majorations_allocations_familiales.droits_ouverts alors
|
||||
majorations_allocations_familiales.montant_versé
|
||||
sinon 0 €)
|
||||
définition montant_base_complément_pour_base_et_majoration égal à
|
||||
montant_versé_base +€ montant_versé_majoration
|
||||
*/
|
||||
|
||||
@Article D521-2|LEGIARTI000030680324@
|
||||
I.-Le montant mensuel de l'allocation forfaitaire prévue au deuxième alinéa de l'article L. 521-1 est défini selon le barème suivant :
|
||||
I.-Le montant mensuel de l'allocation forfaitaire prévue au deuxième alinéa de
|
||||
l'article L. 521-1 est défini selon le barème suivant :
|
||||
|
||||
1° Lorsque le ménage ou la personne a disposé d'un montant de ressources inférieur ou égal au plafond défini au I de l'article D. 521-3, le montant mensuel de l'allocation forfaitaire est fixé à 20,234 % de la base mensuelle de calcul des allocations familiales par enfant ;
|
||||
1° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
inférieur ou égal au plafond défini au I de l'article D. 521-3, le montant
|
||||
mensuel de l'allocation forfaitaire est fixé à 20,234 % de la base mensuelle
|
||||
de calcul des allocations familiales par enfant ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocation_forfaitaire.montant_versé sous condition
|
||||
l521_1.ressources_ménage <= d521_3.plafond_I
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_versé_forfaitaire sous condition
|
||||
ressources_ménage <=€ plafond_I_d521_3
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 20,234 % *
|
||||
nombre pour enfant dans ménage.enfants de
|
||||
allocation_forfaitaire.droits_ouverts de enfant
|
||||
(prestations_familiales.base_mensuelle *€ 20,234 %) *€
|
||||
entier_vers_décimal de (nombre pour enfant dans enfants_à_charge de
|
||||
droit_ouvert_forfaitaire de enfant)
|
||||
*/
|
||||
|
||||
2° Lorsque le ménage ou la personne a disposé d'un montant de ressources supérieur au plafond défini au I de l'article D. 521-3 et inférieur ou égal à celui défini au II du même article, le montant mensuel de l'allocation forfaitaire est fixé à 10,117 % de la base mensuelle de calcul des allocations familiales par enfant ;
|
||||
2° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
supérieur au plafond défini au I de l'article D. 521-3 et inférieur ou égal
|
||||
à celui défini au II du même article, le montant mensuel de l'allocation
|
||||
forfaitaire est fixé à 10,117 % de la base mensuelle de calcul des allocations
|
||||
familiales par enfant ;
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocation_forfaitaire.montant_versé sous condition
|
||||
(l521_1.ressources_ménage > d521_3.plafond_I) et
|
||||
(l521_1.ressources_ménage <= d521_3.plafond_II)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_versé_forfaitaire sous condition
|
||||
(ressources_ménage >€ plafond_I_d521_3) et
|
||||
(ressources_ménage <=€ plafond_II_d521_3)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 10,117 % *
|
||||
nombre pour enfant dans ménage.enfants de
|
||||
allocation_forfaitaire.droits_ouvert de enfant
|
||||
(prestations_familiales.base_mensuelle *€ 10,117 %) *€
|
||||
entier_vers_décimal de (nombre pour enfant dans enfants_à_charge de
|
||||
droit_ouvert_forfaitaire de enfant)
|
||||
*/
|
||||
|
||||
3° Lorsque le ménage ou la personne a disposé d'un montant de ressources supérieur au plafond défini au II de l'article D. 521-3, le montant mensuel de l'allocation forfaitaire est fixé à 5,059 % de la base mensuelle de calcul des allocations familiales par enfant.
|
||||
3° Lorsque le ménage ou la personne a disposé d'un montant de ressources
|
||||
supérieur au plafond défini au II de l'article D. 521-3, le montant mensuel
|
||||
de l'allocation forfaitaire est fixé à 5,059 % de la base mensuelle de calcul
|
||||
des allocations familiales par enfant.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocation_forfaitaire.montant_versé sous condition
|
||||
l521_1.ressources_ménage > d521_3.plafond_II
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_versé_forfaitaire sous condition
|
||||
ressources_ménage >€ plafond_II_d521_3
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle * 5,059 % *
|
||||
nombre pour enfant dans ménage.enfants de
|
||||
allocation_forfaitaire.droits_ouvert de enfant
|
||||
(prestations_familiales.base_mensuelle *€ 5,059 %) *€
|
||||
entier_vers_décimal de (nombre pour enfant dans enfants_à_charge de
|
||||
droit_ouvert_forfaitaire de enfant)
|
||||
*/
|
||||
|
||||
II.-En application du sixième alinéa de l'article L. 521-1, le montant mensuel de l'allocation forfaitaire est majoré d'un complément dégressif lorsque les ressources annuelles du ménage ou de la personne dépassent l'un des plafonds défini au I ou au II de l'article D. 521-3 d'une somme inférieure à douze fois le montant mensuel de l'allocation forfaitaire auquel l'enfant ouvre droit.
|
||||
Ce complément dégressif est égal, pour chaque mois, au douzième de la différence entre, d'une part, ce plafond de ressources majoré de la somme définie à l'alinéa précédent et, d'autre part, le montant des ressources.
|
||||
II.-En application du sixième alinéa de l'article L. 521-1, le montant mensuel
|
||||
de l'allocation forfaitaire est majoré d'un complément dégressif lorsque les
|
||||
ressources annuelles du ménage ou de la personne dépassent l'un des plafonds
|
||||
défini au I ou au II de l'article D. 521-3 d'une somme inférieure à douze fois
|
||||
le montant mensuel de l'allocation forfaitaire auquel l'enfant ouvre droit.
|
||||
Ce complément dégressif est égal, pour chaque mois, au douzième de la différence
|
||||
entre, d'une part, ce plafond de ressources majoré de la somme définie à
|
||||
l'alinéa précédent et, d'autre part, le montant des ressources.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition
|
||||
complément_dégressif.pour_allocation_forfaitaire
|
||||
sous condition
|
||||
allocation_forfaitaire.droits_ouverts et
|
||||
complément_dégressif.droits_ouverts de allocation_forfaitaire.droits_ouverts
|
||||
conséquence égal à
|
||||
(complément_dégressif.dépassement
|
||||
de allocation_forfaitaire.montant_versé) / 12
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_versé_complément_pour_forfaitaire égal à
|
||||
(dépassement_plafond_ressources de montant_versé_forfaitaire) *€
|
||||
(1,0 /. 12,0)
|
||||
*/
|
||||
|
||||
III.-Le nombre minimum d'enfants à charge mentionné au deuxième alinéa de l'article L. 521-1 est fixé à trois.
|
||||
III.-Le nombre minimum d'enfants à charge mentionné au deuxième alinéa de
|
||||
l'article L. 521-1 est fixé à trois.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l521_1.nombre_minimum_enfants égal à 3
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition nombre_enfants_l521_1 égal à 3
|
||||
*/
|
||||
|
||||
@Article D521-3|LEGIARTI000030678079@
|
||||
|
||||
I.-Le plafond prévu au 1° du I des articles D. 521-1 et D. 521-2 est fixé à 55 950 euros. Il est majoré de 5 595 euros par enfant à charge.
|
||||
I.-Le plafond prévu au 1° du I des articles D. 521-1 et D. 521-2 est fixé à
|
||||
55 950 euros. Il est majoré de 5 595 euros par enfant à charge.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition d521_3.plafond_I égal à 55 950 € +
|
||||
5 595 € * nombre de ménage.enfants
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition plafond_I_d521_3 égal à 55 950 € +€
|
||||
5 595 € *€ (entier_vers_décimal de (nombre de enfants_à_charge))
|
||||
*/
|
||||
|
||||
II.-Le plafond prévu au 2° du I des articles D. 521-1 et D. 521-2 est fixé à 78 300 euros. Il est majoré de 5 595 euros par enfant à charge.
|
||||
II.-Le plafond prévu au 2° du I des articles D. 521-1 et D. 521-2 est fixé à
|
||||
78 300 euros. Il est majoré de 5 595 euros par enfant à charge.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition d521_3.plafond_II égal à 78 300 € +
|
||||
5 595 € * nombre de ménage.enfants
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition plafond_II_d521_3 égal à 78 300 € +€
|
||||
5 595 € *€ (entier_vers_décimal de (nombre de enfants_à_charge))
|
||||
*/
|
||||
|
||||
III.-Les montants des plafonds et de leur majoration respective fixés au présent article sont revalorisés au 1er janvier de chaque année, conformément à l'évolution en moyenne annuelle des prix à la consommation hors tabac de l'année civile de référence, par arrêté des ministres chargés de la sécurité sociale, du budget et de l'agriculture.
|
||||
III.-Les montants des plafonds et de leur majoration respective fixés au présent
|
||||
article sont revalorisés au 1er janvier de chaque année, conformément à
|
||||
l'évolution en moyenne annuelle des prix à la consommation hors tabac de
|
||||
l'année civile de référence, par arrêté des ministres chargés de la sécurité
|
||||
sociale, du budget et de l'agriculture.
|
||||
|
||||
@@Livre 7 : Régimes divers - Dispositions diverses@@++
|
||||
|
||||
@ -247,47 +277,51 @@ III.-Les montants des plafonds et de leur majoration respective fixés au prése
|
||||
@@Chapitre 5 : Prestations familiales et prestations assimilées@@++++
|
||||
|
||||
@Article D755-5|LEGIARTI000006738575@
|
||||
I. - Les taux servant au calcul des allocations familiales et de la majoration prévue à l'article L. 755-11 sont identiques à ceux mentionnés à l'article D. 521-1.
|
||||
I. - Les taux servant au calcul des allocations familiales et de la majoration
|
||||
prévue à l'article L. 755-11 sont identiques à ceux mentionnés à l'article
|
||||
D. 521-1.
|
||||
|
||||
/*
|
||||
# Pas de changement à déclarer
|
||||
*/
|
||||
|
||||
II. - En application de l'article L. 755-11, 2e alinéa, le taux servant au calcul des allocations familiales servies pour un seul enfant à charge est fixé à 5,88 p. 100 de la base mensuelle prévue à l'article L. 755-3.
|
||||
II. - En application de l'article L. 755-11, 2e alinéa, le taux servant au
|
||||
calcul des allocations familiales servies pour un seul enfant à charge est
|
||||
fixé à 5,88 p. 100 de la base mensuelle prévue à l'article L. 755-3.
|
||||
|
||||
/*
|
||||
# Composantes des allocations familiales
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.base sous condition
|
||||
l751_1.régime_outre_mer et
|
||||
allocations_familiales.droits_ouverts
|
||||
champ d'application AllocationsFamiliales :
|
||||
|
||||
exception définition_montant_initial_base
|
||||
définition montant_initial_base sous condition
|
||||
prestations_familiales.régime_outre_mer_l751_1 et
|
||||
nombre de enfants_à_charge = 1
|
||||
conséquence égal à
|
||||
si nombre de ménage.enfants = 1 alors
|
||||
allocations_familiales.montant_premier_enfant
|
||||
sinon (
|
||||
allocations_familiales.montant_deuxieme_enfant +
|
||||
allocations_familiales.montant_troisième_enfant_et_plus
|
||||
)
|
||||
montant_initial_base_premier_enfant
|
||||
|
||||
définition allocations_familiales.montant_premier_enfant égal à
|
||||
prestations_familiales.base_mensuelle_dom * 5,88 %
|
||||
définition montant_initial_base_premier_enfant égal à
|
||||
prestations_familiales.base_mensuelle *€ 5,88 %
|
||||
|
||||
# Question : le complément dégressif s'applique-t-il ici ? Car le complément
|
||||
# est basé sur les plafonds du taux de base qui ne s'appliquent pas ici,
|
||||
# donc il serait logique que le plafond ne s'applique pas.
|
||||
*/
|
||||
La majoration des allocations familiales pour un seul enfant à charge est fixée à 3,69 p. 100 de la base mensuelle prévue à l'article L. 755-3 à partir de onze ans et à 5,67 p. 100 à partir de seize ans.
|
||||
La majoration des allocations familiales pour un seul enfant à charge est
|
||||
fixée à 3,69 p. 100 de la base mensuelle prévue à l'article L. 755-3 à
|
||||
partir de onze ans et à 5,67 p. 100 à partir de seize ans.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l521_3.âge_limite_alinéa_1 de enfant
|
||||
sous condition l751_1.régime_outre_mer
|
||||
conséquence égal à 11 an
|
||||
définition
|
||||
majorations_allocations_familiales.base_par_enfant de enfant
|
||||
sous condition
|
||||
l751_1.régime_outre_mer et
|
||||
(majorations_allocations_familiales.droits_ouverts de enfant)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle_dom *
|
||||
(si enfant.âge >= 16 an alors 5,67 % sinon 3,69 %)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition âge_limite_alinéa_1_l521_3 de enfant
|
||||
sous condition prestations_familiales.régime_outre_mer_l751_1
|
||||
conséquence égal à 11
|
||||
|
||||
définition
|
||||
montant_initial_majoration de enfant
|
||||
sous condition
|
||||
prestations_familiales.régime_outre_mer_l751_1 et
|
||||
(nombre de enfants_à_charge = 1)
|
||||
conséquence égal à
|
||||
prestations_familiales.base_mensuelle *€
|
||||
(si enfant.âge >= 16 alors 5,67 % sinon 3,69 %)
|
||||
*/
|
||||
|
@ -11,70 +11,75 @@
|
||||
@Article L511-1|LEGIARTI000038834530@
|
||||
|
||||
Les prestations familiales comprennent :
|
||||
|
||||
1°) la prestation d'accueil du jeune enfant ;
|
||||
|
||||
2°) les allocations familiales ;
|
||||
|
||||
3°) le complément familial ;
|
||||
|
||||
4°) L'allocation de logement régie par les dispositions du livre VIII du code de la construction et de l'habitation ;
|
||||
|
||||
5°) l'allocation d'éducation de l'enfant handicapé ;
|
||||
|
||||
6°) l'allocation de soutien familial ;
|
||||
|
||||
7°) l'allocation de rentrée scolaire ;
|
||||
|
||||
8°) (Abrogé) ;
|
||||
|
||||
9°) l'allocation journalière de présence parentale.
|
||||
/*
|
||||
# Voir métadonnnée L511_1.prestation_courante
|
||||
# Voir l'énumération ÉlémentPrestationsFamiliale
|
||||
*/
|
||||
|
||||
@@Chapitre 2 : Champ d'application@@++++
|
||||
|
||||
@Article L512-3|LEGIARTI000038834523@
|
||||
Sous réserve des règles particulières à chaque prestation, ouvre droit aux prestations familiales :
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
# On suppose dans le programme que tous les enfants déclarés dans
|
||||
# ménage.enfants ouvrent droit aux allocations familiales
|
||||
assertion pour tout enfant dans ménage.enfants on a
|
||||
prestations_familiales.droits_ouverts de enfant
|
||||
*/
|
||||
Sous réserve des règles particulières à chaque prestation,
|
||||
ouvre droit aux prestations familiales :
|
||||
|
||||
1°) tout enfant jusqu'à la fin de l'obligation scolaire ;
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
règle prestations_familiales.droits_ouverts de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(date_calcul <= enfant.fin_obligation_scolaire)
|
||||
champ d'application PrestationsFamiliales :
|
||||
règle droit_ouvert de enfant sous condition
|
||||
(date_courante <=@ enfant.fin_obligation_scolaire)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
2°) après la fin de l'obligation scolaire, et jusqu'à un âge limite, tout enfant dont la rémunération éventuelle n'excède pas un plafond.
|
||||
2°) après la fin de l'obligation scolaire, et jusqu'à un âge limite,
|
||||
tout enfant dont la rémunération éventuelle n'excède pas un plafond.
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
champ d'application PrestationsFamiliales :
|
||||
# On définit les conditions hors âge d'abord car elles
|
||||
# sont référencées dans l'article L521-1
|
||||
règle prestations_familiales.conditions_hors_âge de enfant sous condition
|
||||
(enfant dans ménage.enfants) et (
|
||||
(date_calcul <= enfant.fin_obligation_scolaire) ou
|
||||
(enfant.rémuneration_mensuelle <
|
||||
l512_3.plafond_rémunération_mensuelle_alinéa_2)
|
||||
)
|
||||
# sont référencées plus tard dans l'article L521-1
|
||||
règle conditions_hors_âge de enfant sous condition
|
||||
(date_courante <=@ enfant.fin_obligation_scolaire) ou
|
||||
(enfant.rémuneration_mensuelle <=€ plafond_l512_3_2)
|
||||
conséquence rempli
|
||||
|
||||
règle prestations_familiales.droits_ouverts de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(date_calcul > enfant.fin_obligation_scolaire) et
|
||||
(l512_3.conditions_hors_âge de enfant) et
|
||||
(enfant.âge < l512_3.âge_limite_alinéa_2)
|
||||
règle droit_ouvert de enfant sous condition
|
||||
(date_courante >@ enfant.fin_obligation_scolaire) et
|
||||
(conditions_hors_âge de enfant) et
|
||||
(enfant.âge <= âge_l512_3_2)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
Toutefois, pour l'attribution du complément familial et de l'allocation de logement mentionnés aux 3° et 4° de l'article L. 511-1 , l'âge limite peut être différent de celui mentionné au 2° du présent article.
|
||||
Toutefois, pour l'attribution du complément familial et de l'allocation
|
||||
de logement mentionnés aux 3° et 4° de l'article L. 511-1 , l'âge limite
|
||||
peut être différent de celui mentionné au 2° du présent article.
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
définition l512_3.âge_limite_alinéa_2 sous condition
|
||||
l512_3.âge_limite_alinéa_2_alternatif_utilisé et
|
||||
(l511_1.prestation_courante = ComplémentFamilial ou
|
||||
l511_1.prestation_courante = AllocationLogement)
|
||||
champ d'application PrestationsFamiliales :
|
||||
définition âge_l512_3_2 sous condition
|
||||
(selon âge_l512_3_2_alternatif sous forme
|
||||
-- Présent de âge : vrai
|
||||
-- Absent: faux) et
|
||||
(prestation_courante = ComplémentFamilial ou
|
||||
prestation_courante = AllocationLogement)
|
||||
conséquence égal à
|
||||
l512_3.âge_limite_alinéa_2_alternatif
|
||||
selon âge_l512_3_2_alternatif sous forme
|
||||
-- Présent de âge : âge
|
||||
-- Absent: 0
|
||||
*/
|
||||
|
||||
@@Titre 2 : Prestations générales d'entretien@@+++
|
||||
@ -84,62 +89,62 @@ champ d'application CalculPrestationsFamiliales :
|
||||
@Article L521-1|LEGIARTI000029963006@
|
||||
Les allocations familiales sont dues à partir du deuxième enfant à charge.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
règle allocations_familiales.droits_ouverts sous condition
|
||||
nombre de ménage.enfants >= 2
|
||||
champ d'application AllocationsFamiliales :
|
||||
étiquette définitition_droit_ouvert_base
|
||||
règle droit_ouvert_base sous condition
|
||||
nombre de enfants_à_charge >= 2
|
||||
conséquence rempli
|
||||
|
||||
# Les droits doivent être ouverts pour appliquer le champ
|
||||
# CalculAllocationsFamiliales. Si cette condition n'est pas remplie,
|
||||
# alors il est inutile de calculer le reste
|
||||
assertion allocations_familiales.droits_ouverts
|
||||
# Le champ d'application CalculAllocationsFamiliales inclus
|
||||
# PrestationsFamiliales, mais à condition que la prestation courante
|
||||
# soit cohérente
|
||||
définition l511_1.prestation_courante égal à AllocationFamiliale
|
||||
*/
|
||||
|
||||
Une allocation forfaitaire par enfant d'un montant fixé par décret est versée pendant un an à la personne ou au ménage qui assume la charge d'un nombre minimum d'enfants également fixé par décret lorsque l'un ou plusieurs des enfants qui ouvraient droit aux allocations familiales atteignent l'âge limite mentionné au 2° de l'article L. 512-3 . Cette allocation est versée à la condition que le ou les enfants répondent aux conditions autres que celles de l'âge pour l'ouverture du droit aux allocations familiales.
|
||||
Une allocation forfaitaire par enfant d'un montant fixé par décret est versée
|
||||
pendant un an à la personne ou au ménage qui assume la charge d'un nombre
|
||||
minimum d'enfants également fixé par décret lorsque l'un ou plusieurs des
|
||||
enfants qui ouvraient droit aux allocations familiales atteignent l'âge
|
||||
limite mentionné au 2° de l'article L. 512-3 . Cette allocation est versée
|
||||
à la condition que le ou les enfants répondent aux conditions autres que
|
||||
celles de l'âge pour l'ouverture du droit aux allocations familiales.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
assertion fixé allocation_forfaitaire.montant_versé par décret
|
||||
champ d'application AllocationsFamiliales :
|
||||
assertion fixé montant_versé par décret
|
||||
|
||||
# Ici, l'ouverture du droit aux allocations familiales est conditionné
|
||||
# au fait d'avoir deux enfants à charges qui répondent aux conditions
|
||||
# d'ouverture du droit à une prestation familiale. Pour cette raison,
|
||||
# nous nous référons ici aux conditions_hors_âge de prestations_familiales
|
||||
# et non pas de allocations_familiales
|
||||
règle allocations_familiales.conditions_hors_âge de enfant sous condition
|
||||
règle conditions_hors_âge de enfant sous condition
|
||||
prestations_familiales.conditions_hors_âge de enfant
|
||||
conséquence rempli
|
||||
|
||||
règle allocation_forfaitaire.droits_ouverts de enfant sous condition
|
||||
(nombre de ménage.enfants >= l512_1.nombre_minimum_enfants) et
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.âge = l512_3.âge_limite_alinéa_2) et
|
||||
(allocations_familiales.conditions_hors_âge de enfant)
|
||||
règle droit_ouvert_forfaitaire de enfant sous condition
|
||||
(nombre de enfants_à_charge >= nombre_enfants_l521_1) et
|
||||
(enfant.âge = prestations_familiales.âge_l512_3_2) et
|
||||
(conditions_hors_âge de enfant)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
Le montant des allocations mentionnées aux deux premiers alinéas du présent article, ainsi que celui des majorations mentionnées à l'article L. 521-3 varient en fonction des ressources du ménage ou de la personne qui a la charge des enfants, selon un barème défini par décret.
|
||||
Le montant des allocations mentionnées aux deux premiers alinéas du présent
|
||||
article, ainsi que celui des majorations mentionnées à l'article L. 521-3
|
||||
varient en fonction des ressources du ménage ou de la personne qui a la charge
|
||||
des enfants, selon un barème défini par décret.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
assertion fixé allocations_familiales.montant_versé par décret
|
||||
assertion varie allocations_familiales.montant_versé avec
|
||||
l521_1.ressources_ménage
|
||||
assertion fixé majorations_allocations_familiales.montant_versé par décret
|
||||
assertion varie majorations_allocations_familiales.montant_versé avec
|
||||
l521_1.ressources_ménage
|
||||
champ d'application AllocationsFamiliales :
|
||||
assertion fixé montant_versé_base par décret
|
||||
assertion varie montant_versé_base avec ressources_ménage
|
||||
assertion fixé montant_versé_majorations par décret
|
||||
assertion varie montant_versé_majorations avec ressources_ménage
|
||||
*/
|
||||
|
||||
Le montant des allocations familiales varie en fonction du nombre d'enfants à charge.
|
||||
Le montant des allocations familiales varie en fonction du nombre d'enfants
|
||||
à charge.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
assertion varie allocations_familiales.montant_versé avec
|
||||
nombre de ménage.enfants
|
||||
champ d'application AllocationsFamiliales :
|
||||
assertion varie montant_versé_base.montant_versé avec
|
||||
nombre de enfants_à_charge
|
||||
*/
|
||||
|
||||
Les niveaux des plafonds de ressources, qui varient en fonction du nombre d'enfants à charge, sont révisés conformément à l'évolution annuelle de l'indice des prix à la consommation, hors tabac.
|
||||
Les niveaux des plafonds de ressources, qui varient en fonction du nombre
|
||||
d'enfants à charge, sont révisés conformément à l'évolution annuelle de
|
||||
l'indice des prix à la consommation, hors tabac.
|
||||
/*
|
||||
# Pour formaliser l'évolution des prix, il faudrait recopier ici
|
||||
# tous les décrets d'application qui fixent la valeur des plafonds
|
||||
@ -147,108 +152,111 @@ Les niveaux des plafonds de ressources, qui varient en fonction du nombre d'enfa
|
||||
# nous avons choisi de ne pas inclure tout ce code dans ce document.
|
||||
*/
|
||||
|
||||
Un complément dégressif est versé lorsque les ressources du bénéficiaire dépassent l'un des plafonds, dans la limite de montants définis par décret. Les modalités de calcul de ces montants et celles du complément dégressif sont définies par décret.
|
||||
Un complément dégressif est versé lorsque les ressources du bénéficiaire
|
||||
dépassent l'un des plafonds, dans la limite de montants définis par décret.
|
||||
Les modalités de calcul de ces montants et celles du complément dégressif
|
||||
sont définies par décret.
|
||||
/*
|
||||
# Ditto, le volume du code à inclure pour formaliser cette assertion
|
||||
# est assez important et nous avons choisi de ne pas l'inclure dans ce
|
||||
# document.
|
||||
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
assertion fixé complément_dégressif.montant_versé par décret
|
||||
champ d'application AllocationsFamiliales :
|
||||
assertion fixé montant_versé_complément par décret
|
||||
*/
|
||||
|
||||
@Article L521-2|LEGIARTI000006743210@
|
||||
Les allocations sont versées à la personne qui assume, dans quelques conditions que ce soit, la charge effective et permanente de l'enfant.
|
||||
Les allocations sont versées à la personne qui assume, dans quelques conditions
|
||||
que ce soit, la charge effective et permanente de l'enfant.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.charge_par_enfant de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.prise_en_charge sous forme Complète)
|
||||
conséquence égal à Complète
|
||||
champ d'application AllocationsFamiliales :
|
||||
étiquette définition_prise_en_compte
|
||||
définition prise_en_compte de enfant égal à Complète
|
||||
|
||||
définition allocations_familiales.récipiendaire_par_enfant de enfant
|
||||
sous condition enfant dans ménage.enfants
|
||||
conséquence égal à ménage.parent_en_charge
|
||||
étiquette définition_versement
|
||||
définition versement de enfant égal à Normal
|
||||
*/
|
||||
|
||||
En cas de résidence alternée de l'enfant au domicile de chacun des parents telle que prévue à l'article 373-2-9 du code civil, mise en oeuvre de manière effective, les parents désignent l'allocataire. Cependant, la charge de l'enfant pour le calcul des allocations familiales est partagée par moitié entre les deux parents soit sur demande conjointe des parents, soit si les parents sont en désaccord sur la désignation de l'allocataire. Un décret en Conseil d'Etat fixe les conditions d'application du présent alinéa.
|
||||
En cas de résidence alternée de l'enfant au domicile de chacun des parents telle
|
||||
que prévue à l'article 373-2-9 du code civil, mise en oeuvre de manière
|
||||
effective, les parents désignent l'allocataire. Cependant, la charge de l'enfant
|
||||
pour le calcul des allocations familiales est partagée par moitié entre les deux
|
||||
parents soit sur demande conjointe des parents, soit si les parents sont en
|
||||
désaccord sur la désignation de l'allocataire. Un décret en Conseil d'Etat fixe
|
||||
les conditions d'application du présent alinéa.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
# Premier cas : garde alternée, parents désignent un unique allocataire
|
||||
règle allocations_garde_alternée.unique_allocataire de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.prise_en_charge sous forme GardeAlternée) et
|
||||
((allocations_garde_alternée.choix_allocataire de enfant)
|
||||
sous forme Présent) et
|
||||
((allocations_garde_alternée.choix_allocataire de enfant) -> Présent
|
||||
sous forme UnParent) et
|
||||
(((allocations_garde_alternée.choix_allocataire de enfant) -> Présent -> UnParent)
|
||||
dans ménage.parents)
|
||||
conséquence rempli
|
||||
champ d'application AllocationsFamiliales :
|
||||
# Premier cas : garde alternée, parents désignent un unique allocataire
|
||||
exception définition_prise_en_compte
|
||||
définition prise_en_compte de enfant sous condition
|
||||
selon enfant.garde_alternée sous forme
|
||||
-- OuiAllocataireUnique: vrai
|
||||
-- OuiPartageAllocations: faux
|
||||
-- NonGardeUnique: faux
|
||||
conséquence égal à Complète
|
||||
|
||||
définition allocations_familiales.charge_par_enfant de enfant
|
||||
sous condition allocations_garde_alternée.unique_allocataire de enfant
|
||||
conséquence égal à Complète
|
||||
exception définition_versement
|
||||
définition versement de enfant sous condition
|
||||
selon enfant.garde_alternée sous forme
|
||||
-- OuiAllocataireUnique: vrai
|
||||
-- OuiPartageAllocations: faux
|
||||
-- NonGardeUnique: faux
|
||||
conséquence égal à Normal
|
||||
|
||||
# Deuxième cas : garde alternée, parents partagent la charge pour
|
||||
# l'allocation
|
||||
règle allocations_garde_alternée.allocataire_double de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.prise_en_charge sous forme GardeAlternée) et
|
||||
(enfant.choix_allocataire_garde_alternée sous forme Présent) et
|
||||
(enfant.choix_allocataire_garde_alternée -> Présent sous forme DeuxParents)
|
||||
conséquence rempli
|
||||
|
||||
définition allocations_familiales.charge_par_enfant de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(allocations_garde_alternée.allocataire_double de enfant)
|
||||
conséquence égal à Partagée
|
||||
|
||||
définition allocations_familiales.charge_par_enfant de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.prise_en_charge sous forme GardeAlternée) et
|
||||
(allocations_garde_alternée.demande_conjointe_partage_charge de enfant ou
|
||||
allocations_garde_alternée.desaccord_charge de enfant)
|
||||
# Deuxième cas : garde alternée, parents partagent la charge pour
|
||||
# l'allocation
|
||||
exception définition_prise_en_compte
|
||||
définition prise_en_compte de enfant sous condition
|
||||
selon enfant.garde_alternée sous forme
|
||||
-- OuiPartageAllocations: vrai
|
||||
-- OuiAllocataireUnique: faux
|
||||
-- NonGardeUnique: faux
|
||||
conséquence égal à Partagée
|
||||
|
||||
|
||||
# Quelles variables fixées par R521_2 ?
|
||||
exception définition_versement
|
||||
définition versement de enfant sous condition
|
||||
selon enfant.garde_alternée sous forme
|
||||
-- OuiPartageAllocations: vrai
|
||||
-- OuiAllocataireUnique: faux
|
||||
-- NonGardeUnique: faux
|
||||
conséquence égal à Partagée
|
||||
*/
|
||||
|
||||
Lorsque la personne qui assume la charge effective et permanente de l'enfant ne remplit pas les conditions prévues au titre I du présent livre pour l'ouverture du droit aux allocations familiales, ce droit s'ouvre du chef du père ou, à défaut, du chef de la mère.
|
||||
Lorsque la personne qui assume la charge effective et permanente de l'enfant ne
|
||||
remplit pas les conditions prévues au titre I du présent livre pour l'ouverture
|
||||
du droit aux allocations familiales, ce droit s'ouvre du chef du père ou,
|
||||
à défaut, du chef de la mère.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.récipiendaire_par_enfant de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(titre_I.droits_ouverts_allocations_familiales de
|
||||
(ménage.parent_en_charge de enfant))
|
||||
conséquence égal à ménage.parent1
|
||||
# ménage.parent1 est le père ou à défaut la mère
|
||||
# (ménage.parent2 est optionnel)
|
||||
# Non formalisé pour le calcul du montant des allocations
|
||||
*/
|
||||
|
||||
Lorsqu'un enfant est confié au service d'aide sociale à l'enfance, les allocations familiales continuent d'être évaluées en tenant compte à la fois des enfants présents au foyer et du ou des enfants confiés au service de l'aide sociale à l'enfance. La part des allocations familiales dues à la famille pour cet enfant est versée à ce service. Toutefois, le juge peut décider, d'office ou sur saisine du président du conseil général, à la suite d'une mesure prise en application des articles 375-3 et 375-5 du code civil ou des articles 15,16,16 bis et 28 de l' ordonnance n° 45-174 du 2 février 1945 relative à l'enfance délinquante, de maintenir le versement des allocations à la famille, lorsque celle-ci participe à la prise en charge morale ou matérielle de l'enfant ou en vue de faciliter le retour de l'enfant dans son foyer.
|
||||
Lorsqu'un enfant est confié au service d'aide sociale à l'enfance, les
|
||||
allocations familiales continuent d'être évaluées en tenant compte à la fois
|
||||
des enfants présents au foyer et du ou des enfants confiés au service de
|
||||
l'aide sociale à l'enfance. La part des allocations familiales dues à la
|
||||
famille pour cet enfant est versée à ce service. Toutefois, le juge peut
|
||||
décider, d'office ou sur saisine du président du conseil général, à la
|
||||
suite d'une mesure prise en application des articles 375-3 et 375-5 du code
|
||||
civil ou des articles 15,16,16 bis et 28 de l' ordonnance n° 45-174 du 2
|
||||
février 1945 relative à l'enfance délinquante, de maintenir le versement
|
||||
des allocations à la famille, lorsque celle-ci participe à la prise en
|
||||
charge morale ou matérielle de l'enfant ou en vue de faciliter le retour
|
||||
de l'enfant dans son foyer.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.charge_par_enfant de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.confié_service_social)
|
||||
conséquence égal à Complète
|
||||
|
||||
définition allocations_familiales.récipiendaire_par_enfant de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.confié_service_social)
|
||||
conséquence égal à service_social
|
||||
champ d'application AllocationsFamiliales :
|
||||
exception définition_versement
|
||||
définition versement de enfant sous condition
|
||||
selon enfant.pris_en_charge_par_services_sociaux sous forme
|
||||
-- OuiAllocationVerséeAuxServicesSociaux: vrai
|
||||
-- OuiAllocationVerséeÀLaFamille: faux
|
||||
-- NonPriseEnChargeFamille: faux
|
||||
conséquence égal à AllocationVerséeAuxServicesSociaux
|
||||
*/
|
||||
|
||||
Un décret en Conseil d'Etat fixe les conditions d'application du présent article, notamment dans les cas énumérés ci-dessous :
|
||||
Un décret en Conseil d'Etat fixe les conditions d'application du présent
|
||||
article, notamment dans les cas énumérés ci-dessous :
|
||||
|
||||
a) retrait total de l'autorité parentale des parents ou de l'un d'eux ;
|
||||
|
||||
@ -263,25 +271,27 @@ d) enfants confiés à un service public, à une institution privée, à un part
|
||||
# est confié à un service social.
|
||||
*/
|
||||
|
||||
@Article L521-3|LEGIARTI000006743289@ Chacun des enfants à charge, à l'exception du plus âgé, ouvre droit à partir d'un âge minimum à une majoration des allocations familiales.
|
||||
@Article L521-3|LEGIARTI000006743289@ Chacun des enfants à charge, à
|
||||
l'exception du plus âgé, ouvre droit à partir d'un âge minimum à une
|
||||
majoration des allocations familiales.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
règle majorations_allocations_familiales.droits_ouverts de enfant
|
||||
champ d'application AllocationsFamiliales :
|
||||
règle droit_ouvert_majoration de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant != ménage.enfant_plus_âgé) et
|
||||
(enfant.âge >= l521_3.âge_limite_alinéa_1 de enfant)
|
||||
(non (est_enfant_le_plus_âgé de enfant)) et
|
||||
(enfant.âge >= âge_limite_alinéa_1_l521_3 de enfant)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
Toutefois, les personnes ayant un nombre déterminé d'enfants à charge bénéficient de ladite majoration pour chaque enfant à charge à partir de l'âge mentionné au premier alinéa.
|
||||
Toutefois, les personnes ayant un nombre déterminé d'enfants à charge
|
||||
bénéficient de ladite majoration pour chaque enfant à charge à partir
|
||||
de l'âge mentionné au premier alinéa.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
règle majorations_allocations_familiales.droits_ouverts de enfant
|
||||
champ d'application AllocationsFamiliales :
|
||||
règle droit_ouvert_majoration de enfant
|
||||
sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(nombre de ménage.enfants >= l521_3.minimum_alinéa_2) et
|
||||
(enfant.âge >= l521_3.âge_limite_alinéa_1 de enfant)
|
||||
(nombre de enfants_à_charge >= nombre_enfants_alinéa_2_l521_3) et
|
||||
(enfant.âge >= âge_limite_alinéa_1_l521_3 de enfant)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
@ -290,7 +300,9 @@ champ d'application CalculAllocationsFamiliales :
|
||||
@@Chapitre 1er : Etablissement du salaire de base@@++++
|
||||
|
||||
@Article L551-1|LEGIARTI000031688371@
|
||||
Le montant des prestations familiales est déterminé d'après des bases mensuelles de calcul revalorisées au 1er avril de chaque année par application du coefficient mentionné à l'article L. 161-25 .
|
||||
Le montant des prestations familiales est déterminé d'après des bases
|
||||
mensuelles de calcul revalorisées au 1er avril de chaque année par application
|
||||
du coefficient mentionné à l'article L. 161-25 .
|
||||
/*
|
||||
# Idem que L521-1, on ne formalise pas ici l'évolution de la BMPA
|
||||
champ d'application PrestationsFamiliales :
|
||||
@ -299,70 +311,77 @@ champ d'application PrestationsFamiliales :
|
||||
|
||||
@@Livre 7 : Régimes divers - Dispositions diverses@@++
|
||||
|
||||
@@Titre 5 : Dispositions particulières à la Guadeloupe, à la Guyane, à la
|
||||
Martinique, à La Réunion, à Saint-Barthélemy et à Saint-Martin@@+++
|
||||
@@Titre 5 : Dispositions particulières à la Guadeloupe, à la Guyane, à la Martinique, à La Réunion, à Saint-Barthélemy et à Saint-Martin@@+++
|
||||
|
||||
@@Chapitre 1er : Généralités@@++++
|
||||
|
||||
@Article L751-1|LEGIARTI000031323778@
|
||||
Les dispositions du présent titre s'appliquent en Guadeloupe, en Guyane, en Martinique, à La Réunion, à Saint-Barthélemy et à Saint-Martin à l'ensemble des bénéficiaires de la législation générale de sécurité sociale, y compris les membres des professions agricoles.
|
||||
Les dispositions du présent titre s'appliquent en Guadeloupe, en Guyane,
|
||||
en Martinique, à La Réunion, à Saint-Barthélemy et à Saint-Martin à
|
||||
l'ensemble des bénéficiaires de la législation générale de sécurité
|
||||
sociale, y compris les membres des professions agricoles.
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
règle l751_1.régime_outre_mer sous condition
|
||||
(ménage.résidence = Guadeloupe) ou
|
||||
(ménage.résidence = Guyane) ou
|
||||
(ménage.résidence = Martinique) ou
|
||||
(ménage.résidence = LaRéunion) ou
|
||||
(ménage.résidence = SaintBarthélemy) ou
|
||||
(ménage.résidence = SaintMartin)
|
||||
champ d'application PrestationsFamiliales :
|
||||
règle régime_outre_mer_l751_1 sous condition
|
||||
(résidence = Guadeloupe) ou
|
||||
(résidence = Guyane) ou
|
||||
(résidence = Martinique) ou
|
||||
(résidence = LaRéunion) ou
|
||||
(résidence = SaintBarthélemy) ou
|
||||
(résidence = SaintMartin)
|
||||
conséquence rempli
|
||||
*/
|
||||
|
||||
@@Chapitre 5 : Prestations familiales et prestations assimilées@@++++
|
||||
|
||||
@Article L755-3|LEGIARTI000033728786@
|
||||
Les dispositions des articles L. 512-1 à L. 512-4 , L. 513-1 , L. 521-2 , L. 552-1 , L. 553-1 , L. 553-2 , L. 553-4 , L. 582-1 , L. 582-2 , L. 583-3 et L. 583-5 sont applicables aux collectivités mentionnées à l'article L. 751-1 .
|
||||
Les dispositions des articles L. 512-1 à L. 512-4 , L. 513-1 , L. 521-2 ,
|
||||
L. 552-1 , L. 553-1 , L. 553-2 , L. 553-4 , L. 582-1 , L. 582-2 , L. 583-3
|
||||
et L. 583-5 sont applicables aux collectivités mentionnées à l'article L. 751-1 .
|
||||
|
||||
La base de calcul des prestations familiales est la même que celle qui est fixée en application de l'article L. 551-1 .
|
||||
La base de calcul des prestations familiales est la même que celle qui est
|
||||
fixée en application de l'article L. 551-1 .
|
||||
|
||||
/*
|
||||
champ d'application CalculPrestationsFamiliales :
|
||||
définition prestations_familiales.base_mensuelle_dom égal à
|
||||
prestations_familiales.base_mensuelle
|
||||
*/
|
||||
|
||||
@Article L755-11|LEGIARTI000031323803@
|
||||
Les conditions d'attribution des allocations familiales et de leurs majorations fixées par les articles L. 521-1 et L. 521-3 sont applicables dans les collectivités mentionnées à l'article L. 751-1 .
|
||||
/*
|
||||
# Aucun changement dans le code, puisque les articles restent applicables
|
||||
*/
|
||||
|
||||
Toutefois, les dispositions de l'article L. 755-12 restent en vigueur aussi longtemps que le présent chapitre V est applicable.
|
||||
@Article L755-11|LEGIARTI000031323803@
|
||||
Les conditions d'attribution des allocations familiales et de leurs majorations
|
||||
fixées par les articles L. 521-1 et L. 521-3 sont applicables dans les
|
||||
collectivités mentionnées à l'article L. 751-1 .
|
||||
/*
|
||||
# Aucun changement dans le code, puisque les articles restent applicables
|
||||
*/
|
||||
|
||||
Toutefois, les dispositions de l'article L. 755-12 restent en vigueur aussi
|
||||
longtemps que le présent chapitre V est applicable.
|
||||
|
||||
@Article L755-12|LEGIARTI000029962999@
|
||||
Les allocations familiales sont dues, pour tout enfant, à la personne qui a effectivement la charge de celui-ci.
|
||||
Les allocations familiales sont dues, pour tout enfant, à la personne qui a
|
||||
effectivement la charge de celui-ci.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
règle allocations_familiales.droits_ouverts sous condition
|
||||
(l751_1.régime_outre_mer) et
|
||||
(nombre de ménage.enfants >= 1)
|
||||
champ d'application AllocationsFamiliales:
|
||||
exception définitition_droit_ouvert_base
|
||||
règle droit_ouvert_base sous condition
|
||||
prestations_familiales.régime_outre_mer_l751_1 et
|
||||
(nombre de enfants_à_charge >= 1)
|
||||
conséquence rempli
|
||||
*/
|
||||
Toutefois, les quatre derniers alinéas de l'article L. 521-1 ne sont pas applicables lorsque le ménage ou la personne a un seul enfant à charge.
|
||||
Toutefois, les quatre derniers alinéas de l'article L. 521-1 ne sont pas
|
||||
applicables lorsque le ménage ou la personne a un seul enfant à charge.
|
||||
/*
|
||||
# Ceci concerne l'ouverture du droit à l'allocation forfaitaire
|
||||
# et au complément dégressif.
|
||||
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
règle allocation_forfaitaire.droits_ouverts de enfant sous condition
|
||||
(l751_1.régime_outre_mer) et
|
||||
(nombre de ménage.enfants = 1)
|
||||
champ d'application AllocationsFamiliales :
|
||||
règle droit_ouvert_forfaitaire de enfant sous condition
|
||||
prestations_familiales.régime_outre_mer_l751_1 et
|
||||
(nombre de enfants_à_charge = 1)
|
||||
conséquence non rempli
|
||||
|
||||
règle
|
||||
complément_dégressif.droits_ouvert de allocation
|
||||
sous condition
|
||||
(l751_1.régime_outre_mer) et
|
||||
(nombre de ménage.enfants = 1)
|
||||
règle droit_ouvert_complément sous condition
|
||||
prestations_familiales.régime_outre_mer_l751_1 et
|
||||
(nombre de enfants_à_charge = 1)
|
||||
conséquence non rempli
|
||||
*/
|
||||
|
@ -7,21 +7,28 @@
|
||||
@@Chapitre 2 : Champ d'application.@@++++
|
||||
|
||||
@Article R512-2|LEGIARTI000006750602@
|
||||
Les enfants ouvrent droit aux prestations familiales jusqu'à l'âge de vingt ans sous réserve que leur rémunération n'excède pas le plafond fixé au deuxième alinéa du présent article.
|
||||
Les enfants ouvrent droit aux prestations familiales jusqu'à l'âge de vingt
|
||||
ans sous réserve que leur rémunération n'excède pas le plafond fixé au deuxième
|
||||
alinéa du présent article.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l512_3.âge_limite_alinéa_2 égal à 20 an
|
||||
champ d'application PrestationsFamiliales :
|
||||
définition âge_l512_3_2 égal à 20
|
||||
*/
|
||||
|
||||
Le plafond de rémunération mentionné au 2° de l'article L. 512-3 est égal, pour un mois, à 55 % du salaire minimum interprofessionnel de croissance défini aux articles L. 141-1 à L. 141-9 du code du travail, multiplié par 169.
|
||||
Le plafond de rémunération mentionné au 2° de l'article L. 512-3 est égal, pour
|
||||
un mois, à 55 % du salaire minimum interprofessionnel de croissance défini aux
|
||||
articles L. 141-1 à L. 141-9 du code du travail, multiplié par 169.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l512_3.plafond_rémunération_mensuelle_alinéa_2 égal à
|
||||
55 % * smic.brut_horaire de ménage.résidence * 169
|
||||
champ d'application PrestationsFamiliales :
|
||||
étiquette définition_plafond_l512_3_2
|
||||
définition plafond_l512_3_2 égal à
|
||||
(smic.brut_horaire *€ 55 %) *€ 169,0
|
||||
*/
|
||||
|
||||
Pour ceux des enfants qui bénéficient d'avantages en nature, l'évaluation de ces avantages devra être faite suivant les barèmes fixés pour l'application de la législation sur les assurances sociales.
|
||||
Pour ceux des enfants qui bénéficient d'avantages en nature, l'évaluation de ces
|
||||
avantages devra être faite suivant les barèmes fixés pour l'application de la
|
||||
législation sur les assurances sociales.
|
||||
|
||||
/*
|
||||
# Le programme ne tient pas en compte des avantages en nature
|
||||
@ -32,115 +39,125 @@ Pour ceux des enfants qui bénéficient d'avantages en nature, l'évaluation de
|
||||
@@Chapitre 1er : Allocations familiales@@++++
|
||||
|
||||
@Article R521-1|LEGIARTI000018735853@
|
||||
L'âge mentionné au premier alinéa de l'article L. 521-3 à partir duquel les enfants ouvrent droit à la majoration des allocations familiales est fixé à 14 ans.
|
||||
L'âge mentionné au premier alinéa de l'article L. 521-3 à partir duquel les
|
||||
enfants ouvrent droit à la majoration des allocations familiales est fixé à
|
||||
14 ans.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l521_3.âge_limite_alinéa_1 de enfant égal à 14 an
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition âge_limite_alinéa_1_l521_3 de enfant égal à 14
|
||||
*/
|
||||
|
||||
Le nombre minimum d'enfants à charge, mentionné au deuxième alinéa de l'article L. 521-3 ouvrant droit à ladite majoration pour chaque enfant est fixé à trois.
|
||||
Le nombre minimum d'enfants à charge, mentionné au deuxième alinéa de l'article
|
||||
L. 521-3 ouvrant droit à ladite majoration pour chaque enfant est fixé à trois.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l521_3.minimum_alinéa_2 égal à 3
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition nombre_enfants_alinéa_2_l521_3 égal à 3
|
||||
*/
|
||||
|
||||
NOTA : Décret n° 2008-409 du 28 avril 2008 JORF du 29 avril 2008 art. 2 : Les modifications induites par le décret n° 2008-409 s'appliquent aux enfants dont le onzième anniversaire est postérieur au 30 avril 2008.
|
||||
NOTA : Décret n° 2008-409 du 28 avril 2008 JORF du 29 avril 2008 art. 2 : Les
|
||||
modifications induites par le décret n° 2008-409 s'appliquent aux enfants dont
|
||||
le onzième anniversaire est postérieur au 30 avril 2008.
|
||||
/*
|
||||
# Notons ici un champ d'application différent, correspondant à une ancienne
|
||||
# version du corpus législatif dont un morceau s'applique encore. Nous avons
|
||||
# choisi de montrer ce vieux champ d'application ici plutôt qu'à côté du texte
|
||||
# du décret de 2008 pour des raisons de place seulement.
|
||||
champ d'application CalculAllocationFamilialesAvril2008:
|
||||
# Âge limite avant décret n° 2008-409 du 28 avril 2008
|
||||
définition l521_3.minimum_alinéa_2 de enfant égal à 16 an
|
||||
champ d'application AllocationFamilialesAvril2008:
|
||||
# Âge limite avant décret n° 2008-409 du 28 avril 2008
|
||||
définition âge_limite_alinéa_1_l521_3 égal à 16
|
||||
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l521_3.minimum_alinéa_2 de enfant
|
||||
sous condition (enfant.date_naissance + 11 an <= |30/04/2008|)
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition âge_limite_alinéa_1_l521_3 de enfant
|
||||
sous condition (enfant.date_de_naissance +@ 11 an <=@ |30/04/2008|)
|
||||
conséquence égal à
|
||||
calcul_avril_2008.l521_3.minimum_alinéa_2 de enfant
|
||||
version_avril_2008.âge_limite_alinéa_1_l521_3
|
||||
*/
|
||||
@Article R521-2|LEGIARTI000006750608@
|
||||
Dans les situations visées au deuxième alinéa de l'article L. 521-2 , l'allocataire est celui des deux parents qu'ils désignent d'un commun accord. A défaut d'accord sur la désignation d'un allocataire unique, chacun des deux parents peut se voir reconnaître la qualité d'allocataire :
|
||||
Dans les situations visées au deuxième alinéa de l'article L. 521-2 ,
|
||||
l'allocataire est celui des deux parents qu'ils désignent d'un commun accord.
|
||||
A défaut d'accord sur la désignation d'un allocataire unique, chacun des deux
|
||||
parents peut se voir reconnaître la qualité d'allocataire :
|
||||
|
||||
1° Lorsque les deux parents en ont fait la demande conjointe ;
|
||||
2° Lorsque les deux parents n'ont ni désigné un allocataire unique, ni fait une demande conjointe de partage.
|
||||
|
||||
2° Lorsque les deux parents n'ont ni désigné un allocataire unique, ni fait
|
||||
une demande conjointe de partage.
|
||||
/*
|
||||
# Le fait que l'allocataire dans le cas d'un commun accord soit celui des
|
||||
# deux parents désigné par l'accord est déjà modélisé dans L512-2.
|
||||
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.charge_par_enfant de enfant sous condition
|
||||
(enfant dans ménage.enfants) et
|
||||
(enfant.prise_en_charge sous forme GardeAlternée) et
|
||||
(
|
||||
(allocations_garde_alternée.demande_conjointe_partage_charge) ou
|
||||
(
|
||||
(non allocations_garde_alternée.demande_conjointe_partage_charge) et
|
||||
(enfant.choix_allocataire_garde_alternée sous forme Absent)
|
||||
)
|
||||
)
|
||||
conséquence égal à Partagée
|
||||
*/
|
||||
|
||||
Lorsque les parents ont désigné un allocataire unique ou fait une demande conjointe de partage, ils ne peuvent remettre en cause les modalités ainsi choisies qu'au bout d'un an, sauf modification des modalités de résidence du ou des enfants.
|
||||
Lorsque les parents ont désigné un allocataire unique ou fait une demande
|
||||
conjointe de partage, ils ne peuvent remettre en cause les modalités ainsi
|
||||
choisies qu'au bout d'un an, sauf modification des modalités de résidence du
|
||||
ou des enfants.
|
||||
/*
|
||||
# On ne modélise pas le délai minimal entre changement de situation dans
|
||||
# ce programme.
|
||||
*/
|
||||
|
||||
@Article R521-3|LEGIARTI000006750610@
|
||||
Sous réserve de l'article R. 521-4, dans les situations visées aux 1° et 2° de l'article R. 521-2 , la prestation due à chacun des parents est égale au montant des allocations familiales dues pour le total des enfants à charge, multiplié par un coefficient résultant du rapport entre le nombre moyen d'enfants et le nombre total d'enfants.
|
||||
Sous réserve de l'article R. 521-4, dans les situations visées aux 1° et 2° de
|
||||
l'article R. 521-2 , la prestation due à chacun des parents est égale au
|
||||
montant des allocations familiales dues pour le total des enfants à charge,
|
||||
multiplié par un coefficient résultant du rapport entre le nombre moyen
|
||||
d'enfants et le nombre total d'enfants.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.avec_garde_alternée égal à
|
||||
allocations_familiales.base *
|
||||
allocations_familiales.rapport_enfants_total_moyen
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition montant_avec_garde_alternée_base égal à
|
||||
montant_initial_base *€ rapport_enfants_total_moyen
|
||||
|
||||
définition allocations_familiales.rapport_enfants_total_moyen égal à
|
||||
allocations_familiales.nombre_moyen_enfants /
|
||||
allocations_familiales.nombre_total_enfants
|
||||
définition rapport_enfants_total_moyen égal à
|
||||
nombre_moyen_enfants /. nombre_total_enfants
|
||||
*/
|
||||
|
||||
Le nombre moyen d'enfants, pour chaque foyer, est obtenu en faisant la somme du nombre d'enfants à charge dans les conditions suivantes :
|
||||
Le nombre moyen d'enfants, pour chaque foyer, est obtenu en faisant la somme
|
||||
du nombre d'enfants à charge dans les conditions suivantes :
|
||||
|
||||
1° Chaque enfant en résidence alternée compte pour 0,5 ;
|
||||
|
||||
2° Les autres enfants à charge comptent pour 1.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.nombre_moyen_enfants égal à
|
||||
somme pour enfant dans ménage.enfants de (
|
||||
selon enfant.prise_en_charge sous forme
|
||||
-- Complète : 1
|
||||
-- GardeAlternée de parents : 0,5
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition nombre_moyen_enfants égal à
|
||||
somme décimal pour enfant dans enfants_à_charge de (
|
||||
selon (prise_en_compte de enfant) sous forme
|
||||
-- Complète : 1,0
|
||||
-- Partagée : 0,5
|
||||
)
|
||||
*/
|
||||
Le nombre total d'enfants, pour chaque foyer, est obtenu en faisant la somme du ou des enfants en résidence alternée et, le cas échéant, du ou des autres enfants à charge.
|
||||
Le nombre total d'enfants, pour chaque foyer, est obtenu en faisant la somme du
|
||||
ou des enfants en résidence alternée et, le cas échéant, du ou des autres
|
||||
enfants à charge.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition allocations_familiales.nombre_total_enfants égal à
|
||||
nombre de ménage.enfants
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition nombre_total_enfants égal à
|
||||
entier_vers_décimal de (nombre de enfants_à_charge)
|
||||
*/
|
||||
|
||||
@Article R521-4|LEGIARTI000006750613@
|
||||
Pour l'ouverture du droit à la majoration prévue à l'article L. 521-3 , le nombre d'enfants à charge est évalué dans les conditions prévues au premier alinéa de l'article R. 521-3.
|
||||
Pour l'ouverture du droit à la majoration prévue à l'article L. 521-3 , le
|
||||
nombre d'enfants à charge est évalué dans les conditions prévues au premier
|
||||
alinéa de l'article R. 521-3.
|
||||
|
||||
/*
|
||||
# ménage.enfants ne contient que des enfants à charge donc rien à formaliser
|
||||
# enfants_à_charge ne contient que des enfants à charge donc rien à formaliser
|
||||
# ici
|
||||
*/
|
||||
|
||||
Lorsque le ou les enfants ouvrant droit à ladite majoration sont en résidence alternée, le montant servi au titre de cette majoration est réduit de moitié.
|
||||
Lorsque le ou les enfants ouvrant droit à ladite majoration sont en résidence
|
||||
alternée, le montant servi au titre de cette majoration est réduit de moitié.
|
||||
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
champ d'application AllocationsFamiliales :
|
||||
définition
|
||||
majorations_allocations_familiales.avec_garde_alternée de enfant
|
||||
sous condition enfant dans ménage.enfants
|
||||
montant_avec_garde_alternée_majoration de enfant
|
||||
sous condition enfant dans enfants_à_charge
|
||||
conséquence égal à (
|
||||
selon enfant.prise_en_charge sous forme
|
||||
-- Complète : majorations_allocations_familiales.base_par_enfant
|
||||
-- GardeAlternée de parents:
|
||||
majorations_allocations_familiales.base_par_enfant / 2
|
||||
selon (prise_en_compte de enfant) sous forme
|
||||
-- Complète : montant_initial_majoration de enfant
|
||||
-- Partagée : montant_initial_majoration de enfant *€ 50%
|
||||
)
|
||||
*/
|
||||
|
||||
@ -151,11 +168,15 @@ champ d'application CalculAllocationsFamiliales :
|
||||
@@Chapitre 5 : Prestations familiales et prestations assimilées@@++++
|
||||
|
||||
@Article R755-0-2|LEGIARTI000006752633@
|
||||
Le plafond de rémunération des enfants à charge mentionnés à l'article L. 512-3 est égal, pour un mois, à 55 % du salaire minimum de croissance en vigueur dans chacun des départements mentionnés à l'article L. 751-1 , multiplié par 169.
|
||||
Le plafond de rémunération des enfants à charge mentionnés à l'article
|
||||
L. 512-3 est égal, pour un mois, à 55 % du salaire minimum de croissance en
|
||||
vigueur dans chacun des départements mentionnés à l'article L. 751-1 ,
|
||||
multiplié par 169.
|
||||
/*
|
||||
champ d'application CalculAllocationsFamiliales :
|
||||
définition l512_3.plafond_rémunération_mensuelle_alinéa_2
|
||||
sous condition l751_1.régime_outre_mer
|
||||
champ d'application PrestationsFamiliales :
|
||||
exception définition_plafond_l512_3_2
|
||||
définition plafond_l512_3_2
|
||||
sous condition régime_outre_mer_l751_1
|
||||
conséquence égal à
|
||||
55 % * smic.brut_horaire de ménage.résidence * 169
|
||||
(smic.brut_horaire *€ 55%) *€ 169,0
|
||||
*/
|
||||
|
@ -0,0 +1,70 @@
|
||||
@@Inclusion: ../allocations_familiales.catala_fr@@
|
||||
|
||||
@Tests@
|
||||
|
||||
/*
|
||||
déclaration champ d'application Données:
|
||||
contexte enfant1 contenu Enfant
|
||||
contexte enfant2 contenu Enfant
|
||||
contexte enfant3 contenu Enfant
|
||||
|
||||
champ d'application Données:
|
||||
définition enfant1 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2023|
|
||||
-- date_de_naissance: |01/01/2007|
|
||||
-- âge: 13
|
||||
-- rémuneration_mensuelle: 0€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
définition enfant2 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2021|
|
||||
-- date_de_naissance: |01/01/2009|
|
||||
-- âge: 15
|
||||
-- rémuneration_mensuelle: 0€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: OuiPartageAllocations
|
||||
}
|
||||
définition enfant3 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2019|
|
||||
-- date_de_naissance: |01/01/2003|
|
||||
-- âge: 18
|
||||
-- rémuneration_mensuelle: 400€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
|
||||
déclaration champ d'application Test1:
|
||||
contexte f champ d'application AllocationsFamiliales
|
||||
contexte données champ d'application Données
|
||||
|
||||
champ d'application Test1:
|
||||
définition f.enfants_à_charge égal à
|
||||
[données.enfant1;données.enfant2;données.enfant3]
|
||||
définition f.ressources_ménage égal à 30 000 €
|
||||
définition f.date_courante égal à |01/05/2020|
|
||||
définition f.résidence égal à Métropole
|
||||
assertion f.montant_versé = 417,51€
|
||||
|
||||
déclaration champ d'application Test2:
|
||||
contexte f champ d'application AllocationsFamiliales
|
||||
contexte données champ d'application Données
|
||||
|
||||
champ d'application Test2:
|
||||
définition f.enfants_à_charge égal à [données.enfant1]
|
||||
définition f.ressources_ménage égal à 30 000 €
|
||||
définition f.date_courante égal à |01/05/2020|
|
||||
définition f.résidence égal à Guyane
|
||||
assertion f.montant_versé = 39,63€
|
||||
|
||||
déclaration champ d'application Test3:
|
||||
contexte f champ d'application AllocationsFamiliales
|
||||
contexte données champ d'application Données
|
||||
|
||||
champ d'application Test3:
|
||||
définition f.enfants_à_charge égal à [données.enfant1; données.enfant3]
|
||||
définition f.ressources_ménage égal à 67 250 €
|
||||
définition f.date_courante égal à |01/05/2020|
|
||||
définition f.résidence égal à Métropole
|
||||
assertion f.montant_versé = 388,29€
|
||||
*/
|
@ -0,0 +1,59 @@
|
||||
@@Inclusion: ../allocations_familiales.catala_fr@@
|
||||
|
||||
@Tests@
|
||||
|
||||
/*
|
||||
déclaration champ d'application Données:
|
||||
contexte enfant1 contenu Enfant
|
||||
contexte enfant2 contenu Enfant
|
||||
contexte enfant3 contenu Enfant
|
||||
contexte enfant4 contenu Enfant
|
||||
|
||||
champ d'application Données:
|
||||
définition enfant1 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2023|
|
||||
-- date_de_naissance: |01/01/2007|
|
||||
-- âge: 13
|
||||
-- rémuneration_mensuelle: 0€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
définition enfant2 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2019|
|
||||
-- date_de_naissance: |01/01/2003|
|
||||
-- âge: 18
|
||||
-- rémuneration_mensuelle: 1000€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
définition enfant3 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2019|
|
||||
-- date_de_naissance: |01/01/2003|
|
||||
-- âge: 18
|
||||
-- rémuneration_mensuelle: 400€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
définition enfant4 égal à Enfant {
|
||||
-- fin_obligation_scolaire : |01/08/2016|
|
||||
-- date_de_naissance: |01/01/1999|
|
||||
-- âge: 21
|
||||
-- rémuneration_mensuelle: 0€
|
||||
-- pris_en_charge_par_services_sociaux: NonPriseEnChargeFamille
|
||||
-- garde_alternée: NonGardeUnique
|
||||
}
|
||||
|
||||
déclaration champ d'application Test1:
|
||||
contexte données champ d'application Données
|
||||
contexte f champ d'application PrestationsFamiliales
|
||||
|
||||
champ d'application Test1:
|
||||
définition f.âge_l512_3_2_alternatif égal à Absent
|
||||
définition f.date_courante égal à |01/05/2020|
|
||||
définition f.prestation_courante égal à AllocationsFamiliales
|
||||
définition f.résidence égal à Métropole
|
||||
assertion (f.droit_ouvert de données.enfant1)
|
||||
assertion (non (f.droit_ouvert de données.enfant2))
|
||||
assertion (f.droit_ouvert de données.enfant3)
|
||||
assertion (non (f.droit_ouvert de données.enfant4))
|
||||
*/
|
@ -1,4 +1,4 @@
|
||||
CATALA_LANG=fr
|
||||
SRC=code_general_impots.catala_fr
|
||||
|
||||
include ../Makefile.common
|
||||
include ../Makefile.common.mk
|
||||
|
@ -7,15 +7,20 @@
|
||||
@@0I : Définition du revenu net global@@++++++
|
||||
@Article 1 A@
|
||||
|
||||
Il est établi un impôt annuel unique sur le revenu des personnes physiques désigné sous le nom d'impôt sur le revenu. Cet impôt frappe le revenu net global du contribuable déterminé conformément aux dispositions des articles 156 à 168.
|
||||
Il est établi un impôt annuel unique sur le revenu des personnes physiques
|
||||
désigné sous le nom d'impôt sur le revenu. Cet impôt frappe le revenu net
|
||||
global du contribuable déterminé conformément aux dispositions des
|
||||
articles 156 à 168.
|
||||
|
||||
Ce revenu net global est constitué par le total des revenus nets des catégories suivantes :
|
||||
Ce revenu net global est constitué par le total des revenus nets des
|
||||
catégories suivantes :
|
||||
|
||||
– Revenus fonciers ;
|
||||
|
||||
– Bénéfices industriels et commerciaux ;
|
||||
|
||||
– Rémunérations, d'une part, des gérants majoritaires des sociétés à responsabilité limitée n'ayant pas opté pour le régime fiscal des sociétés de personnes dans les conditions prévues au IV de l'article 3 du décret n° 55-594 du 20 mai 1955 modifié et des gérants des sociétés en commandite par actions et, d'autre part, des associés en nom des sociétés de personnes et des membres des sociétés en participation lorsque ces sociétés ont opté pour le régime fiscal des sociétés de capitaux ;
|
||||
– Rémunérations, d'une part, des gérants majoritaires des sociétés à
|
||||
responsabilité limitée n'ayant pas opté pour le régime fiscal des sociétés de personnes dans les conditions prévues au IV de l'article 3 du décret n° 55-594 du 20 mai 1955 modifié et des gérants des sociétés en commandite par actions et, d'autre part, des associés en nom des sociétés de personnes et des membres des sociétés en participation lorsque ces sociétés ont opté pour le régime fiscal des sociétés de capitaux ;
|
||||
|
||||
– Bénéfices de l'exploitation agricole ;
|
||||
|
||||
@ -25,19 +30,21 @@ Ce revenu net global est constitué par le total des revenus nets des catégorie
|
||||
|
||||
– Revenus de capitaux mobiliers ;
|
||||
|
||||
– Plus-values de cession à titre onéreux de biens ou de droits de toute nature, déterminés conformément aux dispositions des articles 14 à 155, total dont sont retranchées les charges énumérées à l'article 156.
|
||||
– Plus-values de cession à titre onéreux de biens ou de droits de toute
|
||||
nature, déterminés conformément aux dispositions des articles 14 à 155,
|
||||
total dont sont retranchées les charges énumérées à l'article 156.
|
||||
|
||||
/*
|
||||
déclaration structure RevenuNetGlobal:
|
||||
donnée revenus_fonciers contenu montant
|
||||
donnée bénéfices_industriels_commerciaux contenu montant
|
||||
donnée rémunérations_dirigeants contenu montant
|
||||
donnée bénéfices_agricoles contenu montant
|
||||
donnée traitements_salaires contenu montant
|
||||
donnée bénéfices_non_commerciaux contenu montant
|
||||
donnée revenus_capitaux_mobiliers contenu montant
|
||||
donnée plus_values contenu montant
|
||||
donnée total contenu montant
|
||||
donnée revenus_fonciers contenu argent
|
||||
donnée bénéfices_industriels_commerciaux contenu argent
|
||||
donnée rémunérations_dirigeants contenu argent
|
||||
donnée bénéfices_agricoles contenu argent
|
||||
donnée traitements_salaires contenu argent
|
||||
donnée bénéfices_non_commerciaux contenu argent
|
||||
donnée revenus_capitaux_mobiliers contenu argent
|
||||
donnée plus_values contenu argent
|
||||
donnée total contenu argent
|
||||
|
||||
déclaration champ d'application CalculImpotSurLeRevenu :
|
||||
contexte revenu_net_global contenu RevenuNetGlobal
|
||||
|
@ -1,177 +0,0 @@
|
||||
@@The Catala language tutorial@@
|
||||
|
||||
Welcome to this tutorial, whose objective is to guide you through the features of the Catala language and trach you how to annotate a legislative text using the language. This document is addressed primarily to developers or people that have a programming background. It will use terms and jargon that might be unintelligible for lawyers in general.
|
||||
|
||||
@@Literate programming@@+
|
||||
|
||||
To begin writing a Catala program, you must start from the text of the legislative source that will justify the code that you will write. Concretely, that means copy-pasting the text of the law into a Catala source file and formatting it according so that Catala can understand it.
|
||||
|
||||
You can write any kind of plain text in Catala, and it will be printed as is in PDF or HTML output. Keep in mind however that one line in the source file corresponds to a paragraph in the output. Catala allows you to declare section or subsection headers as it is done here, but the fundamental division unit is the article. Let's analyse a fictional example that defines an income tax.
|
||||
|
||||
@Article 1@
|
||||
The income tax for an individual is defined as a fixed percentage of the individual's income over a year.
|
||||
/*
|
||||
# This is a placeholder comment, the code for that article should go here
|
||||
*/
|
||||
|
||||
We will now proceed to encode the algorithmic content of this article using the Catala language. To do that, we will intertwine short snippets of code between the sentences of the legislative text. Each snippet of code should be as short as possible and as close as possible to the actual sentence that justifies the code.
|
||||
|
||||
@@Defining a fictional income tax@@+
|
||||
|
||||
The content of article 1 uses a lot of implicit context : there exists an individual with an income, as well as an income tax that the individual has to pay each year. Even if this implicit context is not verbatim in the law, we have to explicit it for programming purposes. Concretely, we need a "metadata" section that defines the shape and types of the data used inside the law.
|
||||
|
||||
Let's start our metadata section by declaring the type information for the individual and the income tax computation:
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration structure Individual:
|
||||
data income content amount
|
||||
|
||||
declaration structure Article1:
|
||||
data fixed_percentage content decimal
|
||||
data income_tax content amount
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
Each of this declaration is a structure, containing one or more data fields. Structures are useful to group together data that goes together. Usually, you get one structure per concrete object on which the law applies (like the individual), but also one structure for each article that defines quantities (like the article 1). It is up to you to decide how to group the data together, but you should aim to optimize code readability.
|
||||
|
||||
Sometimes, the law gives an enumeration of different situations. These enumerations are modeled in Catala using an enumeration type, like:
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration enumeration TaxCredit:
|
||||
-- NoTaxCredit
|
||||
-- ChildrenTaxCredit content integer # the integer corresponds
|
||||
# to the number of children
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
We've defined and typed the data that the program will manipulate. Now we have to define the logical context in which these data will evolve. This is done in Catala using "scopes". Scopes also have to be declared in metadata, so here we go:
|
||||
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration scope IncomeTaxComputation:
|
||||
context individual content Individual
|
||||
context article1 content Article1
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
This scope declaration says that whenever we're in the scope "IncomeTaxComputation", then we have access to two elements in context, namely the individual's data and the data defined by article 1. We will be able to refer to the lowercase variables in our code, either to use them or to define them or one of their part.
|
||||
|
||||
We now have everything to annotate the contents of article 1, which is copied over below.
|
||||
|
||||
@Article 1@
|
||||
The income tax for an individual is defined as a fixed percentage of the individual's income over a year.
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition article1.income_tax equals
|
||||
invidual.income * article1.fixed_percentage
|
||||
*/
|
||||
|
||||
In the code, we are defining inside our scope the amount of the income tax according to the formula described in the article. When defining formulaes, you have access to all the usual arithmetic operators. But what is the value of that fixed percentage? Often, precise values are defined elsewhere in the legislative source. Here, let's suppose we have:
|
||||
|
||||
@Article 2@
|
||||
The fixed percentage mentionned at article 1 is equal to 20 %.
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition article1.fixed_percentage equals 20 %
|
||||
*/
|
||||
|
||||
You can see here that Catala allows definitions to be scattered throughout the annotation of the legislative text, so that each
|
||||
definition is as close as possible to its location in the text.
|
||||
|
||||
@@Conditional definitions@@
|
||||
|
||||
So far so good, but now the legislative text introduces some trickyness. Let us suppose the third article says:
|
||||
|
||||
@Article 3@ If the individual is in charge of 2 or more children, then the fixed percentage mentionned at article 1 is equal to 15 %.
|
||||
/*
|
||||
# How to redefine article1.fixed_percentage?
|
||||
*/
|
||||
|
||||
This article actually gives another definition for the fixed percentage, which was already defined in article 2. However, article 3 defines the percentage conditionnally to the individual having more than 2 children. Catala allows you precisely to redefine a variable under a condition:
|
||||
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition article1.fixed_percentage under condition
|
||||
individual.number_of_children >= 2
|
||||
consequence equals 15 %
|
||||
*/
|
||||
|
||||
When the Catala program will execute, the right definition will be dynamically chosen by looking at which condition is true. A correctly drafted legislative source should always ensure that at most one condition is true at all times. However, if it is not the case, Catala will let you define a precedence on the conditions, which has to be justified by the law.
|
||||
|
||||
|
||||
@@Functions@@
|
||||
|
||||
Catala lets you define functions anywhere in your data. Here's what it looks like in the metadata definition when we want to define a two-brackets tax computation:
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration structure TwoBrackets:
|
||||
data breakpoint content amount
|
||||
data rate1 content decimal
|
||||
data rate2 content decimal
|
||||
|
||||
declaration scope TwoBracketsTaxComputation :
|
||||
context brackets content TwoBrackets
|
||||
context tax_formula content amount depends on amount
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
And in the code:
|
||||
|
||||
@Article4@ The tax amount for a two-brackets computation is equal to the amount of income in each bracket multiplied by the rate of each bracket.
|
||||
|
||||
/*
|
||||
scope TwoBracketsTaxComputation :
|
||||
definition tax of income equals
|
||||
if income <= breakpoint then
|
||||
income * rate1
|
||||
else (
|
||||
breakpoint * rate1 + (income - breakpoint) * rate2
|
||||
)
|
||||
*/
|
||||
|
||||
@@Scope inclusion@@
|
||||
|
||||
Now that we've defined our helper scope for computing a two-brackets tax, we want to use it in our main tax computation scope.
|
||||
|
||||
@Article 5@ For individuals whose income is greater than $100,000, the income tax of article 1 is computed with a two-brackets system.
|
||||
/*
|
||||
declaration scope IncomeTaxComputation:
|
||||
# The scope inclusion has to be added in the scope declaration
|
||||
context two_brackets_for_rich scope TwoBracketsTaxComputation
|
||||
|
||||
scope IncomeTaxComputation :
|
||||
|
||||
definition article1.income_tax under condition
|
||||
individual.income >= $100,000
|
||||
consequence equals
|
||||
two_brackets_for_rich.tax of individual.income
|
||||
*/
|
||||
|
||||
Scope inclusion also comes with a syntactic sugar for quickly and conditionnaly
|
||||
connecting context quantities :
|
||||
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration scope ExemptedOfTax:
|
||||
context article1 content Article1
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
@Article 6@
|
||||
Individuals earning less than $10,000 are exempted of the income tax mentionned at article 1.
|
||||
/*
|
||||
scope ExemptedOfTax :
|
||||
definition article1.income_tax equals $0
|
||||
|
||||
declaration scope IncomeTaxComputation:
|
||||
# The scope inclusion has to be added in the scope declaration
|
||||
context tax_exempt scope ExemptedOfTax
|
||||
|
||||
scope IncomeTaxComputation:
|
||||
definition article1.income_tax under condition
|
||||
individual.income <= $10,000
|
||||
consequence equals
|
||||
tax_exempt.article1.income_tax
|
||||
*/
|
||||
|
||||
This snippet of code actually brings the definition of article1.income_tax of ExemptedOfTax into the IncomeTaxComputation scope, prefixing it with the "income under $10,000" condition.
|
@ -1,4 +1,4 @@
|
||||
CATALA_LANG=en
|
||||
SRC=tutorial_en.catala_en
|
||||
|
||||
include ../Makefile.common
|
||||
include ../Makefile.common.mk
|
29
examples/tutorial_en/tests/test_tutorial.catala_en
Normal file
@ -0,0 +1,29 @@
|
||||
@@Include: ../tutorial_en.catala_en@@
|
||||
|
||||
@Test@
|
||||
|
||||
/*
|
||||
declaration scope UnitTest1:
|
||||
context tax_computation scope NewIncomeTaxComputation
|
||||
|
||||
scope UnitTest1:
|
||||
definition
|
||||
tax_computation.individual
|
||||
equals
|
||||
Individual {
|
||||
-- income: $230,000
|
||||
-- number_of_children: 0
|
||||
}
|
||||
assertion tax_computation.income_tax = $72,000
|
||||
|
||||
declaration scope UnitTest2:
|
||||
context tax_computation scope NewIncomeTaxComputationFixed
|
||||
|
||||
scope UnitTest2:
|
||||
definition tax_computation.individual equals Individual {
|
||||
-- income: $4,000
|
||||
-- number_of_children: 0
|
||||
}
|
||||
|
||||
assertion tax_computation.income_tax = $0.00
|
||||
*/
|
384
examples/tutorial_en/tutorial_en.catala_en
Normal file
@ -0,0 +1,384 @@
|
||||
@@The Catala language tutorial@@
|
||||
|
||||
Welcome to this tutorial, whose objective is to guide you through the features
|
||||
of the Catala language and teach you how to annotate a legislative text using
|
||||
the language. This document is addressed primarily to developers or people that
|
||||
have a programming background, though tech-savvy lawyers will probably figure
|
||||
things out.
|
||||
|
||||
@@Literate programming@@+
|
||||
|
||||
To begin writing a Catala program, you must start from the text of the
|
||||
legislative source that will justify the code that you will write. Concretely,
|
||||
that means copy-pasting the text of the law into a Catala source file and
|
||||
formatting it according so that Catala can understand it. Catala source files
|
||||
have the ".catala_en" extension. If you were to write a Catala program for a
|
||||
French law, you would use the ".catala_fr" extension.
|
||||
|
||||
You can write any kind of plain text in Catala, and it will be printed as is
|
||||
in PDF or HTML output. You can split your text into short lines, those
|
||||
will appear as a single paragraph in the output. If you want to create a
|
||||
new paragrah, you have to leave a blank line in the source.
|
||||
|
||||
Catala allows you to declare section or subsection headers as it is done
|
||||
here, with the "at" symbol repeated twice. You can define heading of lower
|
||||
importance by adding increasing numbers of "+" after the title of the heading.
|
||||
|
||||
The fundamental division unit is the article, introduced by a single "at".
|
||||
Let's analyse a fictional example that defines an income tax.
|
||||
|
||||
@Article 1@
|
||||
The income tax for an individual is defined as a fixed percentage of the
|
||||
individual's income over a year.
|
||||
|
||||
/*
|
||||
# Welcome to the code mode of Catala. This is a comment, because the line is
|
||||
# prefixed by #.
|
||||
# We will soon learn what to write here in order to translate the meaning
|
||||
# of the article into Catala code.
|
||||
*/
|
||||
|
||||
To do that, we will intertwine short snippets of code between the sentences of
|
||||
the legislative text. Each snippet of code should be as short as possible and
|
||||
as close as possible to the actual sentence that justifies the code. This style
|
||||
is called literate programming, a programming paradigm invented by the famous
|
||||
computer scientist Donald Knuth in the 70s.
|
||||
|
||||
@@Defining a fictional income tax@@+
|
||||
|
||||
The content of article 1 uses a lot of implicit context: there exists an
|
||||
individual with an income, as well as an income tax that the individual has
|
||||
to pay each year. Even if this implicit context is not verbatim in the law,
|
||||
we have to explicit it for programming purposes. Concretely, we need a
|
||||
"metadata" section that defines the shape and types of the data used
|
||||
inside the law.
|
||||
|
||||
Let's start our metadata section by declaring the type information for the
|
||||
individual:
|
||||
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration structure Individual:
|
||||
# The name of the structure "Individual", must start with an
|
||||
# uppercase letter: this is the CamlCase convention.
|
||||
data income content money
|
||||
# In this line, "income" is the name of the structure field and
|
||||
# "money" is the type of what is stored in that field.
|
||||
# Available types include: integer, decimal, money, date, duration,
|
||||
# and any other structure or enumeration that you declare
|
||||
data number_of_children content integer
|
||||
# "income" and "number_of_children" start by a lowercase letter,
|
||||
# they follow the snake_case convention
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
This structure contains two data fields, "income" and "number_of_children". Structures are
|
||||
useful to group together data that goes together. Usually, you
|
||||
get one structure per concrete object on which the law applies (like the
|
||||
individual). It is up to you to decide how to group the data together,
|
||||
but you should aim to optimize code readability.
|
||||
|
||||
Sometimes, the law gives an enumeration of different situations. These
|
||||
enumerations are modeled in Catala using an enumeration type, like:
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration enumeration TaxCredit:
|
||||
# The name "TaxCredit" is also written in CamlCase
|
||||
-- NoTaxCredit
|
||||
# This line says that "TaxCredit" can be a "NoTaxCredit" situation
|
||||
-- ChildrenTaxCredit content integer
|
||||
# This line says that alternatively, "TaxCredit" can be a
|
||||
# "ChildrenTaxCredit" situation. This situation carries a content
|
||||
# of type integer corresponding to the number of children concerned
|
||||
# by the tax credit. This means that if you're in the "ChildrenTaxCredit"
|
||||
# situation, you will also have access to this number of children
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
In computer science terms, such an enumeration is called a "sum type" or simply
|
||||
an enum. The combination of structures and enumerations allow the Catala
|
||||
programmer to declare all possible shapes of data, as they are equivalent to
|
||||
the powerful notion of "algebraic datatypes".
|
||||
|
||||
We've defined and typed the data that the program will manipulate. Now we have
|
||||
to define the logical context in which these data will evolve. This is done in
|
||||
Catala using "scopes". Scopes are close to functions in terms of traditional
|
||||
programming. Scopes also have to be declared in metadata, so here we go:
|
||||
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration scope IncomeTaxComputation:
|
||||
# Scope names use CamlCase
|
||||
context individual content Individual
|
||||
# This line declares a context element of the scope, which is akin to
|
||||
# a function parameter in computer science term. This is the piece of
|
||||
# data on which the scope will operate
|
||||
context fixed_percentage content decimal
|
||||
context income_tax content money
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
We now have everything to annotate the contents of article 1, which is copied
|
||||
over below.
|
||||
|
||||
@Article 1@
|
||||
The income tax for an individual is defined as a fixed percentage of the
|
||||
individual's income over a year.
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition income_tax equals
|
||||
individual.income *$ fixed_percentage
|
||||
*/
|
||||
|
||||
In the code, we are defining inside our scope the amount of the income tax
|
||||
according to the formula described in the article. When defining formulaes,
|
||||
you have access to all the usual arithmetic operators: addition "+",
|
||||
substraction "-", multiplication "*" and division (slash).
|
||||
|
||||
However, in the Catala code, you can see that we use "*$" to multiply the
|
||||
individual income by the fixed percentage. The $ suffix indicates that we
|
||||
are performing a multiplication on an amount of money. Indeed, in Catala,
|
||||
you have to keep track of what you are dealing with: is it money ? Is it
|
||||
an integer? Using just "+" or "*" can be ambiguous in terms of rounding,
|
||||
since money is usually rounded at the cent. So to disambiguate, we suffix these
|
||||
operations with something that indicates the type of what we manipulate.
|
||||
The suffixes are "$" for money "." for decimals, "at" (like in email adresses)
|
||||
for dates and the hat symbol for durations. If you forget the suffix, the Catala type
|
||||
checker will display an error message that will help you put it where it
|
||||
belongs.
|
||||
|
||||
But inside article 1, one question remains unknown: what is the value of
|
||||
of the fixed percentage? Often, precise values are defined elsewhere in the
|
||||
legislative source. Here, let's suppose we have:
|
||||
|
||||
@Article 2@
|
||||
The fixed percentage mentionned at article 1 is equal to 20 %.
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition fixed_percentage equals 20 %
|
||||
# Writing 20% is just an abbreviation for 0.20
|
||||
*/
|
||||
|
||||
You can see here that Catala allows definitions to be scattered throughout
|
||||
the annotation of the legislative text, so that each
|
||||
definition is as close as possible to its location in the text.
|
||||
|
||||
@@Conditional definitions@@+
|
||||
|
||||
So far so good, but now the legislative text introduces some trickyness. Let us
|
||||
suppose the third article says:
|
||||
|
||||
@Article 3@ If the individual is in charge of 2 or more children, then the fixed
|
||||
percentage mentionned at article 1 is equal to 15 %.
|
||||
/*
|
||||
# How to redefine fixed_percentage?
|
||||
*/
|
||||
|
||||
This article actually gives another definition for the fixed percentage, which
|
||||
was already defined in article 2. However, article 3 defines the percentage
|
||||
conditionnally to the individual having more than 2 children. Catala allows
|
||||
you precisely to redefine a variable under a condition:
|
||||
|
||||
/*
|
||||
scope IncomeTaxComputation:
|
||||
definition fixed_percentage under condition
|
||||
individual.number_of_children >= 2
|
||||
consequence equals 15 %
|
||||
# Writing 15% is just an abbreviation for 0.15
|
||||
*/
|
||||
|
||||
When the Catala program will execute, the right definition will be dynamically
|
||||
chosen by looking at which condition is true. A correctly drafted legislative
|
||||
source should always ensure that at most one condition is true at all times.
|
||||
However, if it is not the case, Catala will let you define a precedence on the
|
||||
conditions, which has to be justified by the law.
|
||||
|
||||
|
||||
@@Functions@@+
|
||||
|
||||
Catala lets you define functions anywhere in your data. Here's what it looks
|
||||
like in the metadata definition when we want to define a two-brackets tax
|
||||
computation:
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration structure TwoBrackets:
|
||||
data breakpoint content money
|
||||
data rate1 content decimal
|
||||
data rate2 content decimal
|
||||
|
||||
declaration scope TwoBracketsTaxComputation :
|
||||
context brackets content TwoBrackets
|
||||
context tax_formula content money depends on money
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
And in the code:
|
||||
|
||||
@Article4@ The tax amount for a two-brackets computation is equal to the amount
|
||||
of income in each bracket multiplied by the rate of each bracket.
|
||||
|
||||
/*
|
||||
scope TwoBracketsTaxComputation :
|
||||
definition tax_formula of income equals
|
||||
if income <=$ brackets.breakpoint then
|
||||
income *$ brackets.rate1
|
||||
else (
|
||||
brackets.breakpoint *$ brackets.rate1 +$
|
||||
(income -$ brackets.breakpoint) *$ brackets.rate2
|
||||
)
|
||||
*/
|
||||
|
||||
@@Scope inclusion@@+
|
||||
|
||||
Now that we've defined our helper scope for computing a two-brackets tax, we
|
||||
want to use it in our main tax computation scope.
|
||||
|
||||
@Article 5@ For individuals whose income is greater than $100,000, the income
|
||||
tax of article 1 is 40% of the income above $100,000. Below $100,000, the
|
||||
income tax is 20% of the income.
|
||||
/*
|
||||
declaration scope NewIncomeTaxComputation:
|
||||
context two_brackets scope TwoBracketsTaxComputation
|
||||
# This line says that we add the item two_brackets to the context.
|
||||
# However, the "scope" keyword tells that this item is not a piece of data
|
||||
# but rather a subscope that we can use to compute things.
|
||||
context individual content Individual
|
||||
context income_tax content money
|
||||
|
||||
scope NewIncomeTaxComputation :
|
||||
definition two_brackets.brackets equals TwoBrackets {
|
||||
-- breakpoint: $100,000
|
||||
-- rate1: 20%
|
||||
-- rate2: 40%
|
||||
}
|
||||
definition income_tax equals two_brackets.tax_formula of individual.income
|
||||
*/
|
||||
|
||||
@Article 6@
|
||||
Individuals earning less than $10,000 are exempted of the income tax mentionned
|
||||
at article 1.
|
||||
/*
|
||||
scope NewIncomeTaxComputation:
|
||||
definition income_tax under condition
|
||||
individual.income <=$ $10,000
|
||||
consequence equals $0
|
||||
*/
|
||||
|
||||
That's it! We've defined a two-brackets tax computation simply by annotating
|
||||
legislative article by snippets of Catala code. However, attentive readers
|
||||
may have caught something weird in articles 5 and 6. What happens when the
|
||||
income of the individual is between $10,000 and $100,000 ?
|
||||
|
||||
The law leaves it unspecified ; our dummy articles are clearly badly drafted.
|
||||
But Catala can help you find this sort of errors via simple testing or
|
||||
even formal verification. Let's start with the testing.
|
||||
|
||||
@@Testing Catala programs@@+
|
||||
|
||||
Testing Catala programs can be done directly into Catala. Indeed, writing test
|
||||
cases for each Catala scope that you define is a good practice called
|
||||
"unit testing" in the software engineering community. A test case is defined
|
||||
as another scope:
|
||||
|
||||
@Testing NewIncomeTaxComputation@
|
||||
/*
|
||||
declaration scope Test1:
|
||||
context tax_computation scope NewIncomeTaxComputation
|
||||
|
||||
scope Test1:
|
||||
definition
|
||||
tax_computation.individual
|
||||
# We define the argument to the subscope
|
||||
equals
|
||||
# The four lines below define a whole structure by giving a value to
|
||||
# each of its fields
|
||||
Individual {
|
||||
-- income: $230,000
|
||||
-- number_of_children: 0
|
||||
}
|
||||
|
||||
# Next, we retrieve the income tax value compute it by the subscope and
|
||||
# assert that it is equal to the expected value :
|
||||
# ($230,000-$100,00)*40%+$100,000*20% = $72,000
|
||||
assertion tax_computation.income_tax = $72,000
|
||||
*/
|
||||
|
||||
This test should pass. Let us now consider a failing test case:
|
||||
/*
|
||||
declaration scope Test2:
|
||||
context tax_computation scope NewIncomeTaxComputation
|
||||
|
||||
scope Test2:
|
||||
definition tax_computation.individual equals Individual {
|
||||
-- income: $4,000
|
||||
-- number_of_children: 0
|
||||
}
|
||||
|
||||
assertion tax_computation.income_tax = $0
|
||||
*/
|
||||
|
||||
This test case should compute a $0 income tax because of Article 6. But instead,
|
||||
execution will yield an error saying that there is a conflict between rules.
|
||||
|
||||
@@Defining exceptions to rules@@+
|
||||
|
||||
Indeed, the definition of the income tax in article 6 conflicts with the
|
||||
definition of income tax in article 5. But actually, article 6 is just an
|
||||
exception of article 5. In the law, it is implicit that if article 6 is
|
||||
applicable, then it takes precedence over article 5.
|
||||
|
||||
@Fixing the computation@
|
||||
|
||||
This implicit precedence has to be explicitely declared in Catala. Here is a
|
||||
fixed version of the NewIncomeTaxComputation scope:
|
||||
|
||||
/*
|
||||
declaration scope NewIncomeTaxComputationFixed:
|
||||
context two_brackets scope TwoBracketsTaxComputation
|
||||
context individual content Individual
|
||||
context income_tax content money
|
||||
|
||||
scope NewIncomeTaxComputationFixed :
|
||||
definition two_brackets.brackets equals TwoBrackets {
|
||||
-- breakpoint: $100,000
|
||||
-- rate1: 20%
|
||||
-- rate2: 40%
|
||||
}
|
||||
|
||||
# To define an exception to a rule, you have to first label the rule that
|
||||
# you want to attach to exception to. You can put any snake_case identifier
|
||||
# for the label
|
||||
label article_5
|
||||
definition income_tax equals two_brackets.tax_formula of individual.income
|
||||
|
||||
# Then, you can declare the exception by referring back to the label
|
||||
exception article_5
|
||||
definition income_tax under condition
|
||||
individual.income <=$ $10,000
|
||||
consequence equals $0
|
||||
*/
|
||||
|
||||
And the test that should now work:
|
||||
|
||||
/*
|
||||
declaration scope Test3:
|
||||
context tax_computation scope NewIncomeTaxComputationFixed
|
||||
|
||||
scope Test3:
|
||||
definition tax_computation.individual equals Individual {
|
||||
-- income: $4,000
|
||||
-- number_of_children: 0
|
||||
}
|
||||
assertion tax_computation.income_tax = $0
|
||||
*/
|
||||
|
||||
@@Conclusion@@+
|
||||
|
||||
This tutorial presents the basic concepts and syntax of the Catala language
|
||||
features. It is then up to you use them to annotate legislative texts
|
||||
with their algorithmic translation.
|
||||
|
||||
There is no single way to write Catala programs, as the program style should be
|
||||
adapted to the legislation it annotates. However, Catala is a functional
|
||||
language at heart, so following standard functional programming design patterns
|
||||
should help achieve concise and readable code.
|
13
examples/tutoriel_fr/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
*.aux
|
||||
*.dvi
|
||||
*.fdb_latexmk
|
||||
*.fls
|
||||
*.log
|
||||
*.out
|
||||
*.fls
|
||||
*.tex
|
||||
*.pdf
|
||||
_minted*
|
||||
*.toc
|
||||
*.pyg
|
||||
*.d
|
4
examples/tutoriel_fr/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
CATALA_LANG=fr
|
||||
SRC=tutorial_fr.catala_fr
|
||||
|
||||
include ../Makefile.common.mk
|
29
examples/tutoriel_fr/tests/test_tutoriel.catala_fr
Normal file
@ -0,0 +1,29 @@
|
||||
@@Inclusion: ../tutoriel_fr.catala_fr@@
|
||||
|
||||
@Test@
|
||||
|
||||
/*
|
||||
déclaration champ d'application TestUnitaire1:
|
||||
contexte calcul_impôt champ d'application NouveauCalculImpôtRevenu
|
||||
|
||||
champ d'application TestUnitaire1:
|
||||
définition
|
||||
calcul_impôt.personne
|
||||
égal à
|
||||
Personne {
|
||||
-- revenu: 230 000€
|
||||
-- nombre_enfants: 0
|
||||
}
|
||||
assertion calcul_impôt.impôt_revenu = 72 000€
|
||||
|
||||
déclaration champ d'application TestUnitaire2:
|
||||
contexte calcul_impôt champ d'application NouveauCalculImpôtRevenuCorrect
|
||||
|
||||
champ d'application TestUnitaire2:
|
||||
définition calcul_impôt.personne égal à Personne {
|
||||
-- revenu: 4 000 €
|
||||
-- nombre_enfants: 0
|
||||
}
|
||||
|
||||
assertion calcul_impôt.impôt_revenu = 0,00 €
|
||||
*/
|
407
examples/tutoriel_fr/tutoriel_fr.catala_fr
Normal file
@ -0,0 +1,407 @@
|
||||
@@Tutoriel d'utilisation du langage Catala@@
|
||||
|
||||
Bienvenue dans ce tutoriel, son objectif est de vous accompagner dans les
|
||||
fonctionnalités du langage Catala et de vous apprendre à annoter des textes
|
||||
législatifs avec ce langage. Ce document s'adresse principalement à des développeurs
|
||||
ou des personnes ayant déjà programmé, même si des juristes avec des appétences
|
||||
en informatique devraient pouvoir s'en sortir.
|
||||
|
||||
@@Programmation littéraire@@+
|
||||
|
||||
Pour commencer à écrire un programme Catala, vous devez partir du texte
|
||||
d'une source législative qui va justifier le code que vous écrirez.
|
||||
Concrètement, cela signifie faire un copier-coller du texte de la loi dans
|
||||
un fichier de source Catala et le formatter afin que Catala puisse le comprendre.
|
||||
Les fichiers de source Catala ont l'extension ".catala_en" en version anglaise.
|
||||
Si vous écriviez un programme Catala pour une loi française, vous devrez utiliser
|
||||
l'extension ".catala_fr".
|
||||
|
||||
Vous pouvez écrire n'importe quel texte simple en Catala, cela sera affiché
|
||||
sans modification dans une sortie PDF ou HTML. Vous pouvez découper votre texte
|
||||
en de courtes lignes, cela apparaîtera comme un seul paragraphe dans la sortie.
|
||||
Si vous voulez créer un nouveau paragraphe laisser une ligne vierge dans la source.
|
||||
|
||||
Catala vous permet de déclarer des entêtes de section ou de sous-section,
|
||||
en les précédant et les suivant de deux arobases. Vous pouvez
|
||||
diminuer l'importance du titre en augmentant le nombre de "+" après le titre de
|
||||
l'entête.
|
||||
|
||||
L'unité de division fondamentale est l'article, encadré par un simple arobase.
|
||||
Étudions un exemple ficitif qui définit un impôt sur le revenu.
|
||||
|
||||
@Article 1@
|
||||
L'impôt sur le revenu d'un individu est calculé en tant qu'un pourcentage
|
||||
fixe des revenus d'une personne pour une année.
|
||||
|
||||
/*
|
||||
# Bienvenue dans le mode code de Catala.
|
||||
# Ceci est un commentaire car la ligne débute par le caractère #.
|
||||
# Nous allons bientôt apprendre ce que l'on doit écrire ici pour traduire
|
||||
# le sens d'un article de loi en code Catala.
|
||||
*/
|
||||
|
||||
Afin de faire cela, nous allons entremêler courts bouts de code et phrases
|
||||
du texte législatif. Chaque bout de code devra être aussi court que possible
|
||||
et aussi proche que possible de la phrase qui justifie le code. Ce style
|
||||
s'appelle programmation littéraire, un paradigme de programmation inventé par le
|
||||
célèbre informaticien Donald Knuth dans les années 70.
|
||||
|
||||
@@Définir un impôt sur le revenu fictif@@+
|
||||
|
||||
Le contenu de l'article 1 utilise beaucoup d'éléments du contexte implicite :
|
||||
il existe une personne avec un revenu et en même temps un impôt sur le revenu,
|
||||
qu'une personne doit acquitter chaque année. Même si ce contexte implicite n'est
|
||||
pas inscrit en tant que tel dans la loi, nous devons l'expliciter pour le traduire
|
||||
en code. Concrètement, nous avons besoin d'une section "métadonnées" qui définit
|
||||
la forme et les types de données contenues dans la loi.
|
||||
|
||||
Commençons notre section métadonnées en déclarant l'information sur le type
|
||||
personne :
|
||||
|
||||
@@Début métadonnées@@
|
||||
/*
|
||||
déclaration structure Personne:
|
||||
# Le nom de la structure "Personne", doit commencer
|
||||
# par une lettre majuscule: c'est la convention CamlCase.
|
||||
donnée revenu contenu argent
|
||||
# A cette ligne, revenu est le nom du champ de la structure et
|
||||
# "argent" est le type de de données de ce champ.
|
||||
# On peut utiliser d'autres types comme : entier, décimal,
|
||||
# argent, date, durée ou tout autre structure ou énumération
|
||||
# que vous aurez déclaré
|
||||
donnée nombre_enfants contenu entier
|
||||
# "revenu" and "nombre_enfants" commençent par une lettre minuscule,
|
||||
# ils adhèrent à la convention snake_case
|
||||
*/
|
||||
@@Fin métadonnées@@
|
||||
|
||||
Cette structure contient deux champs de de données, "revenu" et "nombre_enfants".
|
||||
Les structures sont utiles pour regrouper des données qui vont ensemble.
|
||||
Typiquement, vous aurez une structure pour une entité concrète sur laquelle
|
||||
s'applique la loi (comme une personne). C'est à vous de décider comment regrouper
|
||||
les données mais vous devrez viser à optimiser la lisibilité du code.
|
||||
|
||||
Parfois, la loi donne une énumération de différentes situations. Ces énumérations
|
||||
sont modélisés en Catala par le type énumération, comme suit :
|
||||
@@Début métadonnées@@
|
||||
/*
|
||||
déclaration énumération CréditImpôt:
|
||||
# Le nom "CréditImpôt" s'écrit aussi en CamlCase
|
||||
-- AucunCréditImpôt
|
||||
# Cette ligne indique que "CréditImpôt" peut être en situation
|
||||
# "AucunCréditImpôt"
|
||||
-- CréditImpôtEnfants contenu entier
|
||||
# Cette ligne indique, de manière alternative, que "CréditImpôt" peut aussi
|
||||
# être une situation "CréditImpôtEnfants". Cette situation porte un contenu
|
||||
# de type entier qui correspond au nombre d'enfants concernés par le crédit
|
||||
# d'impôt. Cela signifie que si vous êtes dans la situation
|
||||
# "CréditImpôtEnfants", vous aurez aussi accès à ce nombre d'enfants.
|
||||
*/
|
||||
@@Fin métadonnées@@
|
||||
|
||||
En informatique, une telle énumération est appelée "type somme" ou simplement
|
||||
énumération. La combinaison de structures et d'énumération permet au programmeur
|
||||
Catala de déclarer toutes les formes possibles de données, car cette combinaison
|
||||
est équivalente à la puissante notion de types de données algébriques.
|
||||
|
||||
Nous avons défini et typé les données que le programme va manipuler. Maintenant,
|
||||
nous devons définir un contexte logique dans lequel ces données vont évoluer.
|
||||
On effectue cela par la notion de "champs d'application" en Catala.
|
||||
Les champs d'application sont proches des fonctions en termes de programmation
|
||||
traditionnelle. Les champs d'application doivent avoir été déclarés
|
||||
préalablement dans les métadonnées, de la manière suivante:
|
||||
|
||||
@@Début métadonnées@@
|
||||
/*
|
||||
déclaration champ d'application CalculImpôtRevenu:
|
||||
# Les champs d'application utilisent le CamlCase
|
||||
contexte personne contenu Personne
|
||||
# Cette ligne déclare un élémént de contexte du champ d'application,
|
||||
# cela ressemble à un paramètre de fonction en informatique. C'est la
|
||||
# donnée sur laquelle le champ d'application va intervenir
|
||||
contexte pourcentage_fixe contenu décimal
|
||||
contexte impôt_revenu contenu argent
|
||||
*/
|
||||
@@Fin métadonnées@@
|
||||
|
||||
Nous avons maintenant tout ce dont nous avons besoin pour annoter le contenu
|
||||
de l'article 1 qui a été copié ci-dessous.
|
||||
|
||||
@Article 1@
|
||||
L'impôt sur le revenu pour une personne est défini comme un pourcentage fixe
|
||||
des revenus de la personne pour une année.
|
||||
/*
|
||||
champ d'application CalculImpôtRevenu:
|
||||
définition impôt_revenu égal à
|
||||
personne.revenu *€ pourcentage_fixe
|
||||
*/
|
||||
|
||||
Dans le code, nous définissons à l'intérieur de notre champ d'application
|
||||
le montant d'impôt sur le revenu selon la formule décrit dans l'article.
|
||||
Quand nous définissons des fomules, vous avez accès à tous les opérateurs
|
||||
arithmétiques habituels : addition "+", soustraction "-", multiplication "*"
|
||||
et division (barre oblique).
|
||||
|
||||
Toutefois, dans le code Catala, vous pouvez voir que nous utilisons "*€"
|
||||
pour multiplier les revenus d'une personne par le pourcentage fixe. Le
|
||||
suffixe € inique que nous effectuons une multiplication sur une somme d'argent.
|
||||
En effet, en Catala, vous devez rester conscient de la donnée manipulée :
|
||||
est-ce de l'argent ? est-ce un entier ? Utiliser simple "+" ou "*" est ambigu
|
||||
en termes d'arrondis car l'argent est habituellement arrondi au centime.
|
||||
Ainsi, afin d'être clair, nous suffixons ces opérations avec quelque chose
|
||||
qui indique le type de donnée manipulé. Les suffixes sont "€" pour de l'argent,
|
||||
"." pour les décimales, arobase (comme dans les adresses mail) pour les dates et
|
||||
le symbole chapeau pour les durées. Si vous oubliez le suffixe, le vérificateur
|
||||
de types de Catala va afficher une message d'erreur afin de vous aider à le placer
|
||||
comme il le faut.
|
||||
|
||||
Mais dans l'article 1, une question reste sans réponse: quelle est la valeur
|
||||
de la pourcentage fixe? Souvent, des valeurs précises sont définis ailleurs
|
||||
dans les sources législatives. Ici, supposons que nous avons:
|
||||
|
||||
@Article 2@
|
||||
Le pourcentage fixe mentionné à l'article 1 est égal à 20%.
|
||||
/*
|
||||
champ d'application CalculImpôtRevenu:
|
||||
définition pourcentage_fixe égal à 20%
|
||||
# Ecrire 20% est juste une abbréviation pour 0.20
|
||||
*/
|
||||
|
||||
Vous pouvez voir ici que Catala permet des définitions réparties dans toute
|
||||
l'annotation du texte législatif, afin que chaque définition soit le plus
|
||||
proche possible de sa localisation dans le texte.
|
||||
|
||||
@@Définitions conditionnelles@@+
|
||||
|
||||
Jusqu'à là tout va bien mais maintenant le texte législatif présente quelques
|
||||
difficultés. Supposons que le troisième article dispose :
|
||||
|
||||
|
||||
@Article 3@ Si l'individu a à sa charge deux ou plus enfants alors
|
||||
le pourcentage fixe mentionné à l'article 1 vaut 15 %.
|
||||
/*
|
||||
# Comment redéfinir pourcentage_fixe ?
|
||||
*/
|
||||
|
||||
Cet article donne en réalité une autre définition pour le pourcentage fixe,
|
||||
préalablement défini à l'article 2. Toutefois, si l'article 3 définit le pourcentage
|
||||
de manière conditionnelle pour la personne ayant plus de deux enfants.
|
||||
Catala permet de redéfinir précisément une variable sous une condition :
|
||||
|
||||
/*
|
||||
champ d'application CalculImpôtRevenu:
|
||||
définition pourcentage_fixe sous condition
|
||||
personne.nombre_enfants >= 2
|
||||
conséquence égal à 15%
|
||||
# Ecrire 15% est juste une abbréviation pour 0.15
|
||||
*/
|
||||
|
||||
Quand le programme Catala va s'exécuter, la juste définition sera choisie
|
||||
dynamiquement en déterminant laquelle des condition est vraie dans le contexte.
|
||||
Une source législative rédigée correctement doit toujours garantir qu'au maximum
|
||||
une seule condition soit vraie à tout moment. Toutefois, si ce n'est pas le cas,
|
||||
Catala vous permettra de définir un ordre des priorités sur les conditions,
|
||||
qui doit être justifié par un raisonnement juridique.
|
||||
|
||||
@@Fonctions@@+
|
||||
|
||||
Catala vous permet de définir des fonctions partout dans vos données. Voici
|
||||
à quoi cela ressemble dans la définition des métadonnées quand nous voulons
|
||||
définir un calcul de l'impôt sur le revenu à deux tranches :
|
||||
|
||||
@@Début métadonnées@@
|
||||
/*
|
||||
déclaration structure DeuxTranches:
|
||||
donnée seuil contenu argent
|
||||
donnée taux1 contenu décimal
|
||||
donnée taux2 contenu décimal
|
||||
|
||||
déclaration champ d'application CalculImpôtDeuxTranches :
|
||||
contexte tranches contenu DeuxTranches
|
||||
contexte formule_imposition contenu argent dépend de argent
|
||||
*/
|
||||
@@Fin métadonnées@@
|
||||
|
||||
Et dans le code :
|
||||
|
||||
@Article4@ Le montant d'impôt pour le calcul à deux tranches
|
||||
est égal au montant d'impôt dans chaque tranche multiplié
|
||||
par le taux de chaque branche.
|
||||
|
||||
/*
|
||||
champ d'application CalculImpôtDeuxTranches :
|
||||
définition formule_imposition de revenu égal à
|
||||
si revenu <=€ tranches.seuil alors
|
||||
revenu *€ tranches.taux1
|
||||
sinon (
|
||||
tranches.seuil *€ tranches.taux1 +€
|
||||
(revenu -€ tranches.seuil) *€ tranches.taux2
|
||||
)
|
||||
*/
|
||||
|
||||
@@Inclusion de champ d'application@@+
|
||||
|
||||
Maintenant que nous avons défini notre champ d'application utilitaire pour
|
||||
calculer un impôt à deux tranches, nous voulons l'utiliser dans notre champ
|
||||
d'application principal de calcul de l'impôt.
|
||||
|
||||
@Article 5@ Pour les individus dont le revenu est supérieur à 100 000€,
|
||||
l'impôt sur le revenu de l'article 1 est de 40% du revenu au-dessus de
|
||||
100 000€. En dessous de 100 000€, l'impôt sur le revenu est de 20% du revenu.
|
||||
|
||||
/*
|
||||
déclaration champ d'application NouveauCalculImpôtRevenu:
|
||||
contexte deux_tranches champ d'application CalculImpôtDeuxTranches
|
||||
# Cette ligne indique que nous ajoutons l'élément deux_tranches au contexte.
|
||||
# Toutefois, les mots clé "champ d'application" indique que l'élément n'est
|
||||
# pas une donnée mais plutôt un sous-champ d'application qui peut être
|
||||
# utilisé pour calculer des choses.
|
||||
contexte personne contenu Personne
|
||||
contexte impôt_revenu contenu argent
|
||||
|
||||
champ d'application NouveauCalculImpôtRevenu :
|
||||
définition deux_tranches.tranches égal à DeuxTranches {
|
||||
-- seuil: 100 000€
|
||||
-- taux1: 20%
|
||||
-- taux2: 40%
|
||||
}
|
||||
définition impôt_revenu égal à
|
||||
deux_tranches.formule_imposition de personne.revenu
|
||||
*/
|
||||
|
||||
@Article 6@
|
||||
Les personnes ayant moins de 10 000€ de revenus sont exemptés de l'impôt
|
||||
sur le revenu prévu à l'article 1.
|
||||
/*
|
||||
champ d'application NouveauCalculImpôtRevenu:
|
||||
définition impôt_revenu sous condition
|
||||
personne.revenu <=€ 10 000€
|
||||
conséquence égal à 0€
|
||||
*/
|
||||
|
||||
Et voilà ! Nous avons défini un calcul d'impôt à deux tranches en annotant
|
||||
tout simplement un texte législatif par des bouts de code Catala.
|
||||
Cependant, les lecteurs attentifs auront vu quelque chose de curieux dans les
|
||||
articles 5 et 6. Que se passe-t-il si le revenu d'une personne se situe entre
|
||||
10 000€ et 100 000€ ?
|
||||
|
||||
La loi ne le précise pas; nos articles sont clairement mal rédigés.
|
||||
Mais Catala vous aide à trouver ce genre d'erreur par de simples tests ou
|
||||
même la vérification formelle. Commençons par les tests.
|
||||
|
||||
@@Tester les programmes Catala@@+
|
||||
|
||||
Tester les programmes Catala peut se faire directement en Catala. En effet,
|
||||
écrire des cas de tests pour chaque champ d'application Catala que vous
|
||||
définissez est une bonne pratique appelée "tests unitaires" dans la
|
||||
communauté du génie logicielle. Les cas de test sont définis dans des
|
||||
champ d'application :
|
||||
|
||||
@Tester NouveauCalculImpotRevenu@
|
||||
/*
|
||||
déclaration champ d'application Test1:
|
||||
contexte calcul_impôt champ d'application NouveauCalculImpôtRevenu
|
||||
|
||||
champ d'application Test1:
|
||||
définition
|
||||
calcul_impôt.personne
|
||||
# L'on définit le paramètre au sous-champ d'application
|
||||
égal à
|
||||
# Les quatre lignes ci-dessous définissent une structure entière
|
||||
# en valorisant chacun des champs
|
||||
Personne {
|
||||
-- revenu: 230 000€
|
||||
-- nombre_enfants: 0
|
||||
}
|
||||
|
||||
# Ensuite, nous récupérons le montant d'impôt, calculé par
|
||||
# sous-champ d'application et nous assertons qu'il vaut bien
|
||||
# la valeur attendue :
|
||||
# (230 000€ - 100 000€) * 40% + 100 000€ * 20% = 72 000€
|
||||
assertion calcul_impôt.impôt_revenu = 72 000€
|
||||
*/
|
||||
|
||||
Ce test devrait être bon. Maintenant étudions un test en échec :
|
||||
/*
|
||||
déclaration champ d'application Test2:
|
||||
contexte calcul_impôt champ d'application NouveauCalculImpôtRevenu
|
||||
|
||||
champ d'application Test2:
|
||||
définition calcul_impôt.personne égal à Personne {
|
||||
-- revenu: 4 000€
|
||||
-- nombre_enfants: 0
|
||||
}
|
||||
|
||||
assertion calcul_impôt.impôt_revenu = 0€
|
||||
*/
|
||||
|
||||
Ce cas de test devrait calculer un impôt sur le revenu de 0€,
|
||||
en raison de l'article 6. Mais au lieu de cela, l'exécution produira
|
||||
une erreur car il y a un conflit entre les règles.
|
||||
|
||||
@@Définir des exceptions à des règles@@+
|
||||
|
||||
En effet, la définition d'un impôt sur le revenu à l'article 6 entre en
|
||||
conflit avec la définition de l'article 5. Mais en réalité, l'article 6
|
||||
est une simple exception à l'article 5. Dans la loi, il est implicite que
|
||||
si l'article 6 est applicable, alors son application est prioritaire
|
||||
sur l'article 5.
|
||||
|
||||
@Régler correctement le calcul@
|
||||
|
||||
Cette priorité implicite doit être explicitement déclaré en Catala. Voici une
|
||||
version correcte du champ d'application NouveauCalculImpotRevenu :
|
||||
|
||||
/*
|
||||
déclaration champ d'application NouveauCalculImpôtRevenuCorrect:
|
||||
contexte deux_tranches champ d'application CalculImpôtDeuxTranches
|
||||
contexte personne contenu Personne
|
||||
contexte impôt_revenu contenu argent
|
||||
|
||||
champ d'application NouveauCalculImpôtRevenuCorrect :
|
||||
définition deux_tranches.tranches égal à DeuxTranches {
|
||||
-- seuil: 100 000€
|
||||
-- taux1: 20%
|
||||
-- taux2: 40%
|
||||
}
|
||||
|
||||
|
||||
# Pour définir une exception à une règle, vous devez d'abord étiquetter
|
||||
# la règle à laquelle vous voulez attacher l'exception. Vous pouvez mettre
|
||||
# n'importe quel identifiant en snake_case pour l'étiquette.
|
||||
étiquette article_5
|
||||
définition impôt_revenu égal à
|
||||
deux_tranches.formule_imposition de personne.revenu
|
||||
|
||||
# Puis, vous pouvez déclarez l'exception par référence à l'étiquette
|
||||
exception article_5
|
||||
définition impôt_revenu sous condition
|
||||
personne.revenu <=€ 10 000€
|
||||
conséquence égal à 0€
|
||||
*/
|
||||
|
||||
Le test devrait désormais fonctionner :
|
||||
|
||||
/*
|
||||
déclaration champ d'application Test3:
|
||||
contexte calcul_impôt champ d'application NouveauCalculImpôtRevenuCorrect
|
||||
|
||||
champ d'application Test3:
|
||||
définition calcul_impôt.personne égal à Personne {
|
||||
-- revenu: 4 000€
|
||||
-- nombre_enfants: 0
|
||||
}
|
||||
assertion calcul_impôt.impôt_revenu = 0€
|
||||
*/
|
||||
|
||||
@@Conclusion@@+
|
||||
|
||||
Ce tutoriel présente les concepts de base et la syntaxe des fonctionnalités
|
||||
du langage Catala. C'est à vous de les utiliser pour annoter du texte
|
||||
législatif avec leur traduction algorithmique.
|
||||
|
||||
Il n'y pas une seule bonne façon d'écrire des programmes Catala car le style de
|
||||
programmation doit être adapté au texte de loi annoté. Cependant, Catala est un
|
||||
langage basé sur la programmation fonctionnelle, donc suivre les
|
||||
patrons de conception habituels de la programmation fonctionnelle
|
||||
devrait aider à obtenir du code concis et lisible.
|
@ -1,4 +1,4 @@
|
||||
CATALA_LANG=en
|
||||
SRC=us_tax_code.catala_en
|
||||
|
||||
include ../Makefile.common
|
||||
include ../Makefile.common.mk
|
||||
|
@ -1 +1,9 @@
|
||||
@@The US Tax Code@@
|
||||
|
||||
|
||||
@@Begin metadata@@
|
||||
/*
|
||||
declaration structure Person:
|
||||
data id content integer
|
||||
*/
|
||||
@@End metadata@@
|
@ -13,28 +13,33 @@ declaration structure Acquisition:
|
||||
data no_sale_or_exchange_before content boolean
|
||||
|
||||
declaration structure Value:
|
||||
data fair_market content amount depends on date
|
||||
data last_acquisition content amount
|
||||
data net_appreciation content amount
|
||||
data fair_market content money depends on date
|
||||
data last_acquisition content money
|
||||
data net_appreciation content money
|
||||
|
||||
declaration structure Transferor:
|
||||
data basis content amount
|
||||
data basis_known content optional amount
|
||||
data basis content money
|
||||
data basis_known content money
|
||||
data acquisition content Acquisition
|
||||
data gain_or_loss content amount depends on date
|
||||
data gain_or_loss content money depends on date
|
||||
|
||||
declaration scope BasisOfGift:
|
||||
context acquisition content Acquisition
|
||||
context basis_subsection_a content amount
|
||||
context basis content amount
|
||||
context basis_bonus_after_1976 content amount
|
||||
context basis_subsection_a content money
|
||||
context basis content money
|
||||
context basis_bonus_after_1976 content money
|
||||
context transferor content Transferor
|
||||
context gift_tax_paid content amount
|
||||
context gift_tax_paid content money
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
@(a) Gifts after December 31, 1920@
|
||||
If the property was acquired by gift after December 31, 1920, the basis shall be the same as it would be in the hands of the donor or the last preceding owner by whom it was not acquired by gift, except that if such basis (adjusted for the period before the date of the gift as provided in section 1016) is greater than the fair market value of the property at the time of the gift, then for the purpose of determining loss the basis shall be such fair market value.
|
||||
If the property was acquired by gift after December 31, 1920, the basis shall be
|
||||
the same as it would be in the hands of the donor or the last preceding owner by
|
||||
whom it was not acquired by gift, except that if such basis (adjusted for the
|
||||
period before the date of the gift as provided in section 1016) is greater than
|
||||
the fair market value of the property at the time of the gift, then for the
|
||||
purpose of determining loss the basis shall be such fair market value.
|
||||
/*
|
||||
scope BasisOfGift:
|
||||
definition basis_subsection_a equals
|
||||
@ -44,12 +49,20 @@ scope BasisOfGift:
|
||||
transferor.basis
|
||||
|
||||
definition basis under condition
|
||||
acquisition.moment > |01/31/1920| and
|
||||
acquisition.moment > |12/31/1920| and
|
||||
acquisition.method = Gift
|
||||
consequence equals basis_subsection_a
|
||||
|
||||
*/
|
||||
If the facts necessary to determine the basis in the hands of the donor or the last preceding owner are unknown to the donee, the Secretary shall, if possible, obtain such facts from such donor or last preceding owner, or any other person cognizant thereof. If the Secretary finds it impossible to obtain such facts, the basis in the hands of such donor or last preceding owner shall be the fair market value of such property as found by the Secretary as of the date or approximate date at which, according to the best information that the Secretary is able to obtain, such property was acquired by such donor or last preceding owner.
|
||||
If the facts necessary to determine the basis in the hands of the donor or the
|
||||
last preceding owner are unknown to the donee, the Secretary shall, if
|
||||
possible, obtain such facts from such donor or last preceding owner, or any
|
||||
other person cognizant thereof. If the Secretary finds it impossible to obtain
|
||||
such facts, the basis in the hands of such donor or last preceding owner shall
|
||||
be the fair market value of such property as found by the Secretary as of the
|
||||
date or approximate date at which, according to the best information that the
|
||||
Secretary is able to obtain, such property was acquired by such donor or last
|
||||
preceding owner.
|
||||
/*
|
||||
scope BasisOfGift under condition
|
||||
acquisition.moment > |01/31/1920| and
|
||||
@ -63,7 +76,12 @@ scope BasisOfGift under condition
|
||||
|
||||
|
||||
@(b) Transfer in trust after December 31, 1920@
|
||||
If the property was acquired after December 31, 1920, by a transfer in trust (other than by a transfer in trust by a gift, bequest, or devise), the basis shall be the same as it would be in the hands of the grantor increased in the amount of gain or decreased in the amount of loss recognized to the grantor on such transfer under the law applicable to the year in which the transfer was made.
|
||||
If the property was acquired after December 31, 1920, by a transfer in trust
|
||||
(other than by a transfer in trust by a gift, bequest, or devise), the basis
|
||||
shall be the same as it would be in the hands of the grantor increased in the
|
||||
amount of gain or decreased in the amount of loss recognized to the grantor on
|
||||
such transfer under the law applicable to the year in which the transfer was
|
||||
made.
|
||||
/*
|
||||
scope BasisOfGift under condition
|
||||
acquisition.moment > |01/31/1920| and
|
||||
@ -74,7 +92,9 @@ scope BasisOfGift under condition
|
||||
*/
|
||||
|
||||
@(c) Gift or transfer in trust before January 1, 1921@
|
||||
If the property was acquired by gift or transfer in trust on or before December 31, 1920, the basis shall be the fair market value of such property at the time of such acquisition.
|
||||
If the property was acquired by gift or transfer in trust on or before December
|
||||
31, 1920, the basis shall be the fair market value of such property at the time
|
||||
of such acquisition.
|
||||
/*
|
||||
scope BasisOfGift under condition acquisition.moment <= |01/31/1920|:
|
||||
definition basis equals
|
||||
@ -84,7 +104,10 @@ scope BasisOfGift under condition acquisition.moment <= |01/31/1920|:
|
||||
@@(d) Increased basis for gift tax paid@@+
|
||||
|
||||
@(1) In general@ If—
|
||||
(A) the property is acquired by gift on or after September 2, 1958, the basis shall be the basis determined under subsection (a), increased (but not above the fair market value of the property at the time of the gift) by the amount of gift tax paid with respect to such gift, or
|
||||
(A) the property is acquired by gift on or after September 2, 1958, the basis
|
||||
shall be the basis determined under subsection (a), increased (but not above
|
||||
the fair market value of the property at the time of the gift) by the amount
|
||||
of gift tax paid with respect to such gift, or
|
||||
/*
|
||||
scope BasisOfGift under condition
|
||||
acquisition.moment >= |09/02/1958| and
|
||||
@ -98,7 +121,13 @@ scope BasisOfGift under condition
|
||||
gift_tax_paid
|
||||
)
|
||||
*/
|
||||
(B) the property was acquired by gift before September 2, 1958, and has not been sold, exchanged, or otherwise disposed of before such date, the basis of the property shall be increased on such date by the amount of gift tax paid with respect to such gift, but such increase shall not exceed an amount equal to the amount by which the fair market value of the property at the time of the gift exceeded the basis of the property in the hands of the donor at the time of the gift.
|
||||
(B) the property was acquired by gift before September 2, 1958, and has not been
|
||||
sold, exchanged, or otherwise disposed of before such date, the basis of the
|
||||
property shall be increased on such date by the amount of gift tax paid with
|
||||
respect to such gift, but such increase shall not exceed an amount equal to the
|
||||
amount by which the fair market value of the property at the time of the gift
|
||||
exceeded the basis of the property in the hands of the donor at the time of the
|
||||
gift.
|
||||
/*
|
||||
scope BasisOfGift under condition
|
||||
acquisition.moment < |09/02/1958| and
|
||||
@ -118,26 +147,46 @@ scope BasisOfGift under condition
|
||||
*/
|
||||
|
||||
@(2) Amount of tax paid with respect to gift@
|
||||
For purposes of paragraph (1), the amount of gift tax paid with respect to any gift is an amount which bears the same ratio to the amount of gift tax paid under chapter 12 with respect to all gifts made by the donor for the calendar year (or preceding calendar period) in which such gift is made as the amount of such gift bears to the taxable gifts (as defined in section 2503(a) but computed without the deduction allowed by section 2521) made by the donor during such calendar year or period. For purposes of the preceding sentence, the amount of any gift shall be the amount included with respect to such gift in determining (for the purposes of section 2503(a)) the total amount of gifts made during the calendar year or period, reduced by the amount of any deduction allowed with respect to such gift under section 2522 (relating to charitable deduction) or under section 2523 (relating to marital deduction).
|
||||
For purposes of paragraph (1), the amount of gift tax paid with respect to any
|
||||
gift is an amount which bears the same ratio to the amount of gift tax paid under
|
||||
chapter 12 with respect to all gifts made by the donor for the calendar year
|
||||
(or preceding calendar period) in which such gift is made as the amount of such
|
||||
gift bears to the taxable gifts (as defined in section 2503(a) but computed
|
||||
without the deduction allowed by section 2521) made by the donor during such
|
||||
calendar year or period. For purposes of the preceding sentence, the amount of
|
||||
any gift shall be the amount included with respect to such gift in determining
|
||||
(for the purposes of section 2503(a)) the total amount of gifts made during the
|
||||
calendar year or period, reduced by the amount of any deduction allowed with
|
||||
respect to such gift under section 2522 (relating to charitable deduction) or
|
||||
under section 2523 (relating to marital deduction).
|
||||
/*
|
||||
# We don't formalize the amount of gift tax since it would require formalizing other sections of the code
|
||||
# We don't formalize the amount of gift tax since it would require
|
||||
#formalizing other sections of the code
|
||||
*/
|
||||
|
||||
|
||||
@(3) Gifts treated as made one-half by each spouse@
|
||||
For purposes of paragraph (1), where the donor and his spouse elected, under section 2513 to have the gift considered as made one-half by each, the amount of gift tax paid with respect to such gift under chapter 12 shall be the sum of the amounts of tax paid with respect to each half of such gift (computed in the manner provided in paragraph (2)).
|
||||
For purposes of paragraph (1), where the donor and his spouse elected, under
|
||||
section 2513 to have the gift considered as made one-half by each, the amount
|
||||
of gift tax paid with respect to such gift under chapter 12 shall be the sum
|
||||
of the amounts of tax paid with respect to each half of such gift (computed in
|
||||
the manner provided in paragraph (2)).
|
||||
/*
|
||||
# Same here
|
||||
*/
|
||||
|
||||
@(4) Treatment as adjustment to basis@
|
||||
For purposes of section 1016(b), an increase in basis under paragraph (1) shall be treated as an adjustment under section 1016(a).
|
||||
For purposes of section 1016(b), an increase in basis under paragraph (1) shall
|
||||
be treated as an adjustment under section 1016(a).
|
||||
/*
|
||||
# Same here
|
||||
*/
|
||||
|
||||
@(5) Application to gifts before 1955@
|
||||
With respect to any property acquired by gift before 1955, references in this subsection to any provision of this title shall be deemed to refer to the corresponding provision of the Internal Revenue Code of 1939 or prior revenue laws which was effective for the year in which such gift was made.
|
||||
With respect to any property acquired by gift before 1955, references in this
|
||||
subsection to any provision of this title shall be deemed to refer to the
|
||||
corresponding provision of the Internal Revenue Code of 1939 or prior revenue
|
||||
laws which was effective for the year in which such gift was made.
|
||||
/*
|
||||
# Same here
|
||||
*/
|
||||
@ -145,8 +194,13 @@ With respect to any property acquired by gift before 1955, references in this su
|
||||
@@(6) Special rule for gifts made after December 31, 1976@@++
|
||||
|
||||
@(A) In general@
|
||||
In the case of any gift made after December 31, 1976, the increase in basis provided by this subsection with respect to any gift for the gift tax paid under chapter 12 shall be an amount (not in excess of the amount of tax so paid) which bears the same ratio to the amount of tax so paid as—
|
||||
In the case of any gift made after December 31, 1976, the increase in basis
|
||||
provided by this subsection with respect to any gift for the gift tax paid under
|
||||
chapter 12 shall be an amount (not in excess of the amount of tax so paid) which
|
||||
bears the same ratio to the amount of tax so paid as—
|
||||
|
||||
(i) the net appreciation in value of the gift, bears to
|
||||
|
||||
(ii) the amount of the gift.
|
||||
/*
|
||||
scope BasisOfGift under condition
|
||||
@ -161,7 +215,9 @@ scope BasisOfGift under condition
|
||||
*/
|
||||
|
||||
@(B) Net appreciation@
|
||||
For purposes of paragraph (1), the net appreciation in value of any gift is the amount by which the fair market value of the gift exceeds the donor’s adjusted basis immediately before the gift.
|
||||
For purposes of paragraph (1), the net appreciation in value of any gift is the
|
||||
amount by which the fair market value of the gift exceeds the donor’s adjusted
|
||||
basis immediately before the gift.
|
||||
/*
|
||||
scope BasisOfGift:
|
||||
definition value.net_appreciation equals
|
||||
@ -169,7 +225,9 @@ scope BasisOfGift:
|
||||
*/
|
||||
|
||||
@(e) Gifts between spouses@
|
||||
In the case of any property acquired by gift in a transfer described in section 1041(a), the basis of such property in the hands of the transferee shall be determined under section 1041(b)(2) and not this section.
|
||||
In the case of any property acquired by gift in a transfer described in section
|
||||
1041(a), the basis of such property in the hands of the transferee shall be
|
||||
determined under section 1041(b)(2) and not this section.
|
||||
/*
|
||||
# Same here
|
||||
*/
|
||||
|
@ -24,7 +24,10 @@ declaration scope Section121:
|
||||
|
||||
@(a) Exclusion@
|
||||
|
||||
Gross income shall not include gain from the sale or exchange of property if, during the 5-year period ending on the date of the sale or exchange, such property has been owned and used by the taxpayer as the taxpayer’s principal residence for periods aggregating 2 years or more.
|
||||
Gross income shall not include gain from the sale or exchange of property if,
|
||||
during the 5-year period ending on the date of the sale or exchange, such
|
||||
property has been owned and used by the taxpayer as the taxpayer’s principal
|
||||
residence for periods aggregating 2 years or more.
|
||||
/*
|
||||
# Regulation 1.121-1(c)(1): 2 years = 730 days
|
||||
# Regulation 1.121-1(c)(1): the periods of ownage and usage
|
||||
@ -61,7 +64,8 @@ scope Section121:
|
||||
|
||||
@(1) In general@
|
||||
|
||||
The amount of gain excluded from gross income under subsection (a) with respect to any sale or exchange shall not exceed $250,000.
|
||||
The amount of gain excluded from gross income under subsection (a) with
|
||||
respect to any sale or exchange shall not exceed $250,000.
|
||||
/*
|
||||
scope Section121:
|
||||
definition gain_cap equals $250,000
|
||||
@ -79,7 +83,8 @@ scope Section121:
|
||||
|
||||
@(2) Special rules for joint returns@
|
||||
|
||||
In the case of a husband and wife who make a joint return for the taxable year of the sale or exchange of the property—
|
||||
In the case of a husband and wife who make a joint return for the taxable year
|
||||
of the sale or exchange of the property—
|
||||
|
||||
/*
|
||||
# What if the current taxable year is not the taxable
|
||||
@ -115,26 +120,41 @@ scope Section1 under condition
|
||||
*/
|
||||
|
||||
@(B) Other joint returns@
|
||||
If such spouses do not meet the requirements of subparagraph (A), the limitation under paragraph (1) shall be the sum of the limitations under paragraph (1) to which each spouse would be entitled if such spouses had not been married. For purposes of the preceding sentence, each spouse shall be treated as owning the property during the period that either spouse owned the property.
|
||||
If such spouses do not meet the requirements of subparagraph (A), the limitation
|
||||
under paragraph (1) shall be the sum of the limitations under paragraph (1) to
|
||||
which each spouse would be entitled if such spouses had not been married. For
|
||||
purposes of the preceding sentence, each spouse shall be treated as owning the
|
||||
property during the period that either spouse owned the property.
|
||||
|
||||
@@(3) Application to only 1 sale or exchange every 2 years@@++
|
||||
|
||||
Subsection (a) shall not apply to any sale or exchange by the taxpayer if, during the 2-year period ending on the date of such sale or exchange, there was any other sale or exchange by the taxpayer to which subsection (a) applied.
|
||||
Subsection (a) shall not apply to any sale or exchange by the taxpayer if,
|
||||
during the 2-year period ending on the date of such sale or exchange, there
|
||||
was any other sale or exchange by the taxpayer to which subsection (a) applied.
|
||||
|
||||
@@(4) Special rule for certain sales by surviving spouses@@++
|
||||
|
||||
In the case of a sale or exchange of property by an unmarried individual whose spouse is deceased on the date of such sale, paragraph (1) shall be applied by substituting “$500,000” for “$250,000” if such sale occurs not later than 2 years after the date of death of such spouse and the requirements of paragraph (2)(A) were met immediately before such date of death.
|
||||
In the case of a sale or exchange of property by an unmarried individual whose
|
||||
spouse is deceased on the date of such sale, paragraph (1) shall be applied by
|
||||
substituting “$500,000” for “$250,000” if such sale occurs not later than 2
|
||||
years after the date of death of such spouse and the requirements of paragraph
|
||||
(2)(A) were met immediately before such date of death.
|
||||
|
||||
@@(5) Exclusion of gain allocated to nonqualified use@@++
|
||||
|
||||
@(A) In general@
|
||||
|
||||
Subsection (a) shall not apply to so much of the gain from the sale or exchange of property as is allocated to periods of nonqualified use.
|
||||
Subsection (a) shall not apply to so much of the gain from the sale or exchange
|
||||
of property as is allocated to periods of nonqualified use.
|
||||
|
||||
@(B) Gain allocated to periods of nonqualified use@
|
||||
|
||||
For purposes of subparagraph (A), gain shall be allocated to periods of nonqualified use based on the ratio which—
|
||||
(i) the aggregate periods of nonqualified use during the period such property was owned by the taxpayer, bears to
|
||||
For purposes of subparagraph (A), gain shall be allocated to periods of
|
||||
nonqualified use based on the ratio which—
|
||||
|
||||
(i) the aggregate periods of nonqualified use during the period such property
|
||||
was owned by the taxpayer, bears to
|
||||
|
||||
(ii) the period such property was owned by the taxpayer.
|
||||
|
||||
@@(C) Period of nonqualified use@@+++
|
||||
@ -143,21 +163,33 @@ For purposes of this paragraph—
|
||||
|
||||
@(i) In general@
|
||||
|
||||
The term “period of nonqualified use” means any period (other than the portion of any period preceding January 1, 2009) during which the property is not used as the principal residence of the taxpayer or the taxpayer’s spouse or former spouse.
|
||||
The term “period of nonqualified use” means any period (other than the portion
|
||||
of any period preceding January 1, 2009) during which the property is not used
|
||||
as the principal residence of the taxpayer or the taxpayer’s spouse or former
|
||||
spouse.
|
||||
|
||||
@(ii) Exceptions@
|
||||
|
||||
The term “period of nonqualified use” does not include—
|
||||
|
||||
(I) any portion of the 5-year period described in subsection (a) which is after the last date that such property is used as the principal residence of the taxpayer or the taxpayer’s spouse,
|
||||
(I) any portion of the 5-year period described in subsection (a) which is after
|
||||
the last date that such property is used as the principal residence of the
|
||||
taxpayer or the taxpayer’s spouse,
|
||||
|
||||
(II) any period (not to exceed an aggregate period of 10 years) during which the taxpayer or the taxpayer’s spouse is serving on qualified official extended duty (as defined in subsection (d)(9)(C)) described in clause (i), (ii), or (iii) of subsection (d)(9)(A), and
|
||||
(II) any period (not to exceed an aggregate period of 10 years) during which the
|
||||
taxpayer or the taxpayer’s spouse is serving on qualified official extended duty
|
||||
(as defined in subsection (d)(9)(C)) described in clause (i), (ii), or (iii) of
|
||||
subsection (d)(9)(A), and
|
||||
|
||||
(III) any other period of temporary absence (not to exceed an aggregate period of 2 years) due to change of employment, health conditions, or such other unforeseen circumstances as may be specified by the Secretary.
|
||||
(III) any other period of temporary absence (not to exceed an aggregate period
|
||||
of 2 years) due to change of employment, health conditions, or such other
|
||||
unforeseen circumstances as may be specified by the Secretary.
|
||||
@(D) Coordination with recognition of gain attributable to depreciation@
|
||||
|
||||
For purposes of this paragraph—
|
||||
|
||||
(i) subparagraph (A) shall be applied after the application of subsection (d)(6), and
|
||||
(i) subparagraph (A) shall be applied after the application of subsection
|
||||
(d)(6), and
|
||||
|
||||
(ii) subparagraph (B) shall be applied without regard to any gain to which subsection (d)(6) applies.
|
||||
(ii) subparagraph (B) shall be applied without regard to any gain to which
|
||||
subsection (d)(6) applies.
|
||||
|
@ -8,13 +8,23 @@ declaration enumeration DiscountType:
|
||||
-- Services
|
||||
|
||||
declaration scope QualifiedEmployeeDiscount:
|
||||
context customer_price content amount
|
||||
context employee_price content amount
|
||||
context customer_price content money
|
||||
context employee_price content money
|
||||
context gross_profit_percentage content decimal
|
||||
context qualified_employee_discount content amount
|
||||
context employee_discount content amount
|
||||
context aggregate_cost content amount
|
||||
context qualified_employee_discount content money
|
||||
context employee_discount content money
|
||||
context aggregate_cost content money
|
||||
context discount_type content DiscountType
|
||||
context is_property content boolean
|
||||
context is_services content boolean
|
||||
|
||||
scope QualifiedEmployeeDiscount:
|
||||
definition is_property equals match discount_type with pattern
|
||||
-- Property: true
|
||||
-- Services: false
|
||||
definition is_services equals match discount_type with pattern
|
||||
-- Property: false
|
||||
-- Services: true
|
||||
*/
|
||||
@@End metadata@@
|
||||
|
||||
@ -23,46 +33,61 @@ declaration scope QualifiedEmployeeDiscount:
|
||||
For purposes of this section—
|
||||
|
||||
@(1) Qualified employee discount@
|
||||
The term “qualified employee discount” means any employee discount with respect to qualified property or services to the extent such discount does not exceed—
|
||||
(A) in the case of property, the gross profit percentage of the price at which the property is being offered by the employer to customers, or
|
||||
The term “qualified employee discount” means any employee discount with respect
|
||||
to qualified property or services to the extent such discount does not exceed—
|
||||
(A) in the case of property, the gross profit percentage of the price at which
|
||||
the property is being offered by the employer to customers, or
|
||||
/*
|
||||
scope QualifiedEmployeeDiscount :
|
||||
definition qualified_employee_discount
|
||||
under condition discount_type with pattern Property consequence
|
||||
under condition is_property consequence
|
||||
equals
|
||||
if employee_discount >
|
||||
customer_price * gross_profit_percentage
|
||||
then customer_price * gross_profit_percentage
|
||||
if employee_discount >$
|
||||
customer_price *$ gross_profit_percentage
|
||||
then customer_price *$ gross_profit_percentage
|
||||
else employee_discount
|
||||
*/
|
||||
(B) in the case of services, 20 percent of the price at which the services are being offered by the employer to customers.
|
||||
(B) in the case of services, 20 percent of the price at which the services are
|
||||
being offered by the employer to customers.
|
||||
/*
|
||||
scope QualifiedEmployeeDiscount :
|
||||
definition qualified_employee_discount
|
||||
under condition discount_type with pattern Services consequence
|
||||
under condition is_services consequence
|
||||
equals
|
||||
if employee_discount >
|
||||
customer_price * 20%
|
||||
then customer_price * 20%
|
||||
if employee_discount >$
|
||||
customer_price *$ 20%
|
||||
then customer_price *$ 20%
|
||||
else employee_discount
|
||||
|
||||
scope QualifiedEmployeeDiscount under condition is_services:
|
||||
# When selling a service, one does not need the aggregate cost.
|
||||
# We provide a default value here so that the computations run smooth.
|
||||
definition aggregate_cost equals $0
|
||||
definition gross_profit_percentage equals 0%
|
||||
*/
|
||||
@@(2) Gross profit percentage@@++
|
||||
|
||||
@(A) In general@
|
||||
The term “gross profit percentage” means the percent which—
|
||||
(i) the excess of the aggregate sales price of property sold by the employer to customers over the aggregate cost of such property to the employer, is of
|
||||
|
||||
(i) the excess of the aggregate sales price of property sold by the employer
|
||||
to customers over the aggregate cost of such property to the employer, is of
|
||||
|
||||
(ii) the aggregate sale price of such property.
|
||||
/*
|
||||
scope QualifiedEmployeeDiscount
|
||||
under condition discount_type with pattern Property :
|
||||
assertion customer_price >= aggregate_cost
|
||||
scope QualifiedEmployeeDiscount under condition is_property:
|
||||
assertion customer_price >=$ aggregate_cost
|
||||
|
||||
definition gross_profit_percentage equals
|
||||
(customer_price - aggregate_cost) / customer_price
|
||||
(customer_price -$ aggregate_cost) /$ customer_price
|
||||
*/
|
||||
@(B) Determination of gross profit percentage@
|
||||
Gross profit percentage shall be determined on the basis of—
|
||||
(i) all property offered to customers in the ordinary course of the line of business of the employer in which the employee is performing services (or a reasonable classification of property selected by the employer), and
|
||||
|
||||
(i) all property offered to customers in the ordinary course of the line of
|
||||
business of the employer in which the employee is performing services (or a
|
||||
reasonable classification of property selected by the employer), and
|
||||
|
||||
(ii) the employer’s experience during a representative period.
|
||||
/*
|
||||
# (i) and (ii) are subjective criteria for determining the gross profit
|
||||
@ -70,17 +95,25 @@ Gross profit percentage shall be determined on the basis of—
|
||||
*/
|
||||
@(3) Employee discount defined@
|
||||
The term “employee discount” means the amount by which—
|
||||
(A) the price at which the property or services are provided by the employer to an employee for use by such employee, is less than
|
||||
(B) the price at which such property or services are being offered by the employer to customers.
|
||||
|
||||
(A) the price at which the property or services are provided by the employer to
|
||||
an employee for use by such employee, is less than
|
||||
|
||||
(B) the price at which such property or services are being offered by the
|
||||
employer to customers.
|
||||
/*
|
||||
scope QualifiedEmployeeDiscount:
|
||||
assertion customer_price >= employee_price
|
||||
assertion customer_price >=$ employee_price
|
||||
|
||||
definition employee_discount equals
|
||||
employee_price - customer_price
|
||||
customer_price -$ employee_price
|
||||
*/
|
||||
@(4) Qualified property or services@
|
||||
The term “qualified property or services” means any property (other than real property and other than personal property of a kind held for investment) or services which are offered for sale to customers in the ordinary course of the line of business of the employer in which the employee is performing services.
|
||||
The term “qualified property or services” means any property (other than real
|
||||
property and other than personal property of a kind held for investment) or
|
||||
services which are offered for sale to customers in the ordinary course of
|
||||
the line of business of the employer in which the employee is performing
|
||||
services.
|
||||
/*
|
||||
# Again, this is for subjectively determining what item qualifies for a
|
||||
# discount, not formalizing
|
||||
|
42
examples/us_tax_code/tests/test_section_132.catala_en
Normal file
@ -0,0 +1,42 @@
|
||||
@@Include: ../section_132.catala_en@@
|
||||
|
||||
@Test@
|
||||
/*
|
||||
declaration scope TestSection132_1:
|
||||
context section_132 scope QualifiedEmployeeDiscount
|
||||
|
||||
scope TestSection132_1:
|
||||
definition section_132.customer_price equals $1500
|
||||
definition section_132.employee_price equals $1000
|
||||
definition section_132.aggregate_cost equals $900
|
||||
definition section_132.discount_type equals Property
|
||||
assertion section_132.employee_discount = $500
|
||||
assertion section_132.gross_profit_percentage = 0.4
|
||||
assertion section_132.qualified_employee_discount = $500
|
||||
*/
|
||||
|
||||
/*
|
||||
declaration scope TestSection132_2:
|
||||
context section_132 scope QualifiedEmployeeDiscount
|
||||
|
||||
scope TestSection132_2:
|
||||
definition section_132.customer_price equals $1500
|
||||
definition section_132.employee_price equals $1000
|
||||
definition section_132.aggregate_cost equals $1200
|
||||
definition section_132.discount_type equals Property
|
||||
assertion section_132.employee_discount = $500
|
||||
assertion section_132.gross_profit_percentage = 0.2
|
||||
assertion section_132.qualified_employee_discount = $300.00
|
||||
*/
|
||||
|
||||
/*
|
||||
declaration scope TestSection132_3:
|
||||
context section_132 scope QualifiedEmployeeDiscount
|
||||
|
||||
scope TestSection132_3:
|
||||
definition section_132.customer_price equals $1500
|
||||
definition section_132.employee_price equals $1000
|
||||
definition section_132.discount_type equals Services
|
||||
assertion section_132.employee_discount = $500
|
||||
assertion section_132.qualified_employee_discount = $300
|
||||
*/
|
@ -7,12 +7,15 @@ if [[ $1 == "" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make website-assets
|
||||
|
||||
rsync -a _build/default/_doc/_html/ $1/ocaml_docs/
|
||||
scp examples/allocations_familiales/allocations_familiales.html $1/
|
||||
scp examples/us_tax_code/us_tax_code.html $1/
|
||||
scp examples/tutorial/tutorial_en.html $1/
|
||||
scp examples/tutorial_en/tutorial_en.html $1/
|
||||
scp examples/tutoriel_fr/tutoriel_fr.html $1/
|
||||
scp grammar.html $1/
|
||||
scp catala.html $1/
|
||||
scp legifrance_catala.html $1/
|
||||
scp _build/default/src/catala_web/catala_web.bc.js $1/playground/
|
||||
scp examples/tutorial_en/tutorial_en.catala_en $1/playground/
|
||||
scp examples/tutoriel_fr/tutoriel_fr.catala_fr $1/playground/
|
||||
scp syntax_highlighting/en/ace/mode-catala_en.js $1/playground/
|
||||
scp syntax_highlighting/fr/ace/mode-catala_fr.js $1/playground/
|
@ -1,3 +0,0 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
sh <(curl -sL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)
|
@ -12,18 +12,29 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Abstract syntax tree built by the Catala parser *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
|
||||
type constructor = string
|
||||
(** Constructors are CamlCase *)
|
||||
|
||||
type ident = string
|
||||
(** Idents are snake_case *)
|
||||
|
||||
type qident = ident Pos.marked list
|
||||
|
||||
type primitive_typ = Integer | Decimal | Boolean | Money | Text | Date | Named of constructor
|
||||
type primitive_typ =
|
||||
| Integer
|
||||
| Decimal
|
||||
| Boolean
|
||||
| Money
|
||||
| Duration
|
||||
| Text
|
||||
| Date
|
||||
| Named of constructor
|
||||
|
||||
type base_typ_data =
|
||||
| Primitive of primitive_typ
|
||||
| Collection of base_typ_data Pos.marked
|
||||
| Optional of base_typ_data Pos.marked
|
||||
type base_typ_data = Primitive of primitive_typ | Collection of base_typ_data Pos.marked
|
||||
|
||||
type base_typ = Condition | Data of base_typ_data
|
||||
|
||||
@ -53,13 +64,35 @@ type enum_decl = {
|
||||
|
||||
type match_case_pattern = constructor Pos.marked list * ident Pos.marked option
|
||||
|
||||
type binop = And | Or | Add | Sub | Mult | Div | Lt | Lte | Gt | Gte | Eq | Neq
|
||||
type op_kind =
|
||||
| KInt (** No suffix *)
|
||||
| KDec (** Suffix: [.] *)
|
||||
| KMoney (** Suffix: [$] *)
|
||||
| KDate (** Suffix: [@] *)
|
||||
| KDuration (** Suffix: [^] *)
|
||||
|
||||
type unop = Not | Minus
|
||||
type binop =
|
||||
| And
|
||||
| Or
|
||||
| Add of op_kind
|
||||
| Sub of op_kind
|
||||
| Mult of op_kind
|
||||
| Div of op_kind
|
||||
| Lt of op_kind
|
||||
| Lte of op_kind
|
||||
| Gt of op_kind
|
||||
| Gte of op_kind
|
||||
| Eq
|
||||
| Neq
|
||||
|
||||
type builtin_expression = Cardinal | Now
|
||||
type unop = Not | Minus of op_kind
|
||||
|
||||
type aggregate_func = AggregateSum | AggregateCount
|
||||
type builtin_expression = Cardinal | IntToDec
|
||||
|
||||
type aggregate_func =
|
||||
| AggregateSum of primitive_typ
|
||||
| AggregateCount
|
||||
| AggregateExtremum of bool (* true if max *) * primitive_typ
|
||||
|
||||
type literal_date = {
|
||||
literal_date_day : int Pos.marked;
|
||||
@ -67,13 +100,13 @@ type literal_date = {
|
||||
literal_date_year : int Pos.marked;
|
||||
}
|
||||
|
||||
type literal_number = Int of int | Dec of int * int
|
||||
type literal_number = Int of Z.t | Dec of Z.t * Z.t
|
||||
|
||||
type literal_unit = Percent | Year | Month | Day
|
||||
|
||||
type collection_op = Exists | Forall | Aggregate of aggregate_func
|
||||
|
||||
type money_amount = { money_amount_units : int; money_amount_cents : int }
|
||||
type money_amount = { money_amount_units : Z.t; money_amount_cents : Z.t }
|
||||
|
||||
type literal =
|
||||
| Number of literal_number Pos.marked * literal_unit Pos.marked option
|
||||
@ -88,11 +121,6 @@ type match_case = {
|
||||
|
||||
and match_cases = match_case Pos.marked list
|
||||
|
||||
and struct_inject = {
|
||||
struct_inject_name : constructor Pos.marked;
|
||||
struct_inject_fields : (ident Pos.marked * expression Pos.marked) list;
|
||||
}
|
||||
|
||||
and expression =
|
||||
| MatchWith of expression Pos.marked * match_cases Pos.marked
|
||||
| IfThenElse of expression Pos.marked * expression Pos.marked * expression Pos.marked
|
||||
@ -107,12 +135,15 @@ and expression =
|
||||
| Literal of literal
|
||||
| EnumInject of constructor Pos.marked * expression Pos.marked option
|
||||
| EnumProject of expression Pos.marked * constructor Pos.marked
|
||||
| StructLit of constructor Pos.marked * (ident Pos.marked * expression Pos.marked) list
|
||||
| ArrayLit of expression Pos.marked list
|
||||
| Ident of ident
|
||||
| Dotted of expression Pos.marked * ident Pos.marked
|
||||
(* Dotted is for both struct field projection and sub-scope variables *)
|
||||
| StructInject of struct_inject
|
||||
(** Dotted is for both struct field projection and sub-scope variables *)
|
||||
|
||||
type rule = {
|
||||
rule_label : ident Pos.marked option;
|
||||
rule_exception_to : ident Pos.marked option;
|
||||
rule_parameter : ident Pos.marked option;
|
||||
rule_condition : expression Pos.marked option;
|
||||
rule_name : qident Pos.marked;
|
||||
@ -120,6 +151,8 @@ type rule = {
|
||||
}
|
||||
|
||||
type definition = {
|
||||
definition_label : ident Pos.marked option;
|
||||
definition_exception_to : ident Pos.marked option;
|
||||
definition_name : qident Pos.marked;
|
||||
definition_parameter : ident Pos.marked option;
|
||||
definition_condition : expression Pos.marked option;
|
||||
@ -189,14 +222,12 @@ type law_include =
|
||||
| CatalaFile of string Pos.marked
|
||||
| LegislativeText of string Pos.marked
|
||||
|
||||
type law_article_item =
|
||||
| LawText of string
|
||||
| CodeBlock of code_block * source_repr
|
||||
| LawInclude of law_include
|
||||
type law_article_item = LawText of string | CodeBlock of code_block * source_repr
|
||||
|
||||
type law_heading = { law_heading_name : string; law_heading_precedence : int }
|
||||
|
||||
type law_structure =
|
||||
| LawInclude of law_include
|
||||
| LawHeading of law_heading * law_structure list
|
||||
| LawArticle of law_article * law_article_item list
|
||||
| MetadataBlock of code_block * source_repr
|
@ -12,223 +12,745 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
open Catala_ast
|
||||
open Lambda_ast
|
||||
(** Translation from {!module: Surface.Ast} to {!module: Desugaring.Ast}.
|
||||
|
||||
(** The optional argument subdef allows to choose between differents uids in case the expression is
|
||||
a redefinition of a subvariable *)
|
||||
let rec expr_to_lambda (scope : Uid.Scope.t) (def_key : Uid.ScopeDef.t option)
|
||||
(ctxt : Name_resolution.context) ((expr, pos) : Catala_ast.expression Pos.marked) :
|
||||
Lambda_ast.term =
|
||||
let scope_ctxt = Uid.ScopeMap.find scope ctxt.scopes in
|
||||
let rec_helper = expr_to_lambda scope def_key ctxt in
|
||||
- Removes syntactic sugars
|
||||
- Separate code from legislation *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module Cli = Utils.Cli
|
||||
|
||||
(** {1 Translating expressions} *)
|
||||
|
||||
let translate_op_kind (k : Ast.op_kind) : Dcalc.Ast.op_kind =
|
||||
match k with
|
||||
| KInt -> KInt
|
||||
| KDec -> KRat
|
||||
| KMoney -> KMoney
|
||||
| KDate -> KDate
|
||||
| KDuration -> KDuration
|
||||
|
||||
let translate_binop (op : Ast.binop) : Dcalc.Ast.binop =
|
||||
match op with
|
||||
| And -> And
|
||||
| Or -> Or
|
||||
| Add l -> Add (translate_op_kind l)
|
||||
| Sub l -> Sub (translate_op_kind l)
|
||||
| Mult l -> Mult (translate_op_kind l)
|
||||
| Div l -> Div (translate_op_kind l)
|
||||
| Lt l -> Lt (translate_op_kind l)
|
||||
| Lte l -> Lte (translate_op_kind l)
|
||||
| Gt l -> Gt (translate_op_kind l)
|
||||
| Gte l -> Gte (translate_op_kind l)
|
||||
| Eq -> Eq
|
||||
| Neq -> Neq
|
||||
|
||||
let translate_unop (op : Ast.unop) : Dcalc.Ast.unop =
|
||||
match op with Not -> Not | Minus l -> Minus (translate_op_kind l)
|
||||
|
||||
(** The two modules below help performing operations on map with the {!type: Bindlib.box}. Indeed,
|
||||
Catala uses the {{:https://lepigre.fr/ocaml-bindlib/} Bindlib} library to represent bound
|
||||
variables in the AST. In this translation, bound variables are used to represent function
|
||||
parameters or pattern macthing bindings. *)
|
||||
|
||||
module LiftStructFieldMap = Bindlib.Lift (Scopelang.Ast.StructFieldMap)
|
||||
module LiftEnumConstructorMap = Bindlib.Lift (Scopelang.Ast.EnumConstructorMap)
|
||||
|
||||
(** Usage: [translate_expr scope ctxt expr]
|
||||
|
||||
Translates [expr] into its desugared equivalent. [scope] is used to disambiguate the scope and
|
||||
subscopes variables than occur in the expresion *)
|
||||
let rec translate_expr (scope : Scopelang.Ast.ScopeName.t) (ctxt : Name_resolution.context)
|
||||
((expr, pos) : Ast.expression Pos.marked) : Scopelang.Ast.expr Pos.marked Bindlib.box =
|
||||
let scope_ctxt = Scopelang.Ast.ScopeMap.find scope ctxt.scopes in
|
||||
let rec_helper = translate_expr scope ctxt in
|
||||
match expr with
|
||||
| IfThenElse (e_if, e_then, e_else) ->
|
||||
((EIfThenElse (rec_helper e_if, rec_helper e_then, rec_helper e_else), pos), TDummy)
|
||||
Bindlib.box_apply3
|
||||
(fun e_if e_then e_else -> (Scopelang.Ast.EIfThenElse (e_if, e_then, e_else), pos))
|
||||
(rec_helper e_if) (rec_helper e_then) (rec_helper e_else)
|
||||
| Binop (op, e1, e2) ->
|
||||
let op_term = (Pos.same_pos_as (EOp (Binop (Pos.unmark op))) op, TDummy) in
|
||||
((EApp (op_term, [ rec_helper e1; rec_helper e2 ]), pos), TDummy)
|
||||
let op_term =
|
||||
Pos.same_pos_as (Scopelang.Ast.EOp (Dcalc.Ast.Binop (translate_binop (Pos.unmark op)))) op
|
||||
in
|
||||
Bindlib.box_apply2
|
||||
(fun e1 e2 -> (Scopelang.Ast.EApp (op_term, [ e1; e2 ]), pos))
|
||||
(rec_helper e1) (rec_helper e2)
|
||||
| Unop (op, e) ->
|
||||
let op_term = (Pos.same_pos_as (EOp (Unop (Pos.unmark op))) op, TDummy) in
|
||||
((EApp (op_term, [ rec_helper e ]), pos), TDummy)
|
||||
let op_term =
|
||||
Pos.same_pos_as (Scopelang.Ast.EOp (Dcalc.Ast.Unop (translate_unop (Pos.unmark op)))) op
|
||||
in
|
||||
Bindlib.box_apply (fun e -> (Scopelang.Ast.EApp (op_term, [ e ]), pos)) (rec_helper e)
|
||||
| Literal l ->
|
||||
let untyped_term =
|
||||
match l with
|
||||
| Number ((Int i, _), _) -> EInt i
|
||||
| Number ((Dec (i, f), _), _) -> EDec (i, f)
|
||||
| Bool b -> EBool b
|
||||
| _ -> Name_resolution.raise_unsupported_feature "literal" pos
|
||||
| Number ((Int i, _), None) -> Scopelang.Ast.ELit (Dcalc.Ast.LInt i)
|
||||
| Number ((Int i, _), Some (Percent, _)) ->
|
||||
Scopelang.Ast.ELit (Dcalc.Ast.LRat (Q.div (Q.of_bigint i) (Q.of_int 100)))
|
||||
| Number ((Dec (i, f), _), None) ->
|
||||
let digits_f =
|
||||
try int_of_float (ceil (float_of_int (Z.log2 f) *. log 2.0 /. log 10.0))
|
||||
with Invalid_argument _ -> 0
|
||||
in
|
||||
Scopelang.Ast.ELit
|
||||
(Dcalc.Ast.LRat
|
||||
Q.(of_bigint i + (of_bigint f / of_bigint (Z.pow (Z.of_int 10) digits_f))))
|
||||
| Number ((Dec (i, f), _), Some (Percent, _)) ->
|
||||
let digits_f =
|
||||
try int_of_float (ceil (float_of_int (Z.log2 f) *. log 2.0 /. log 10.0))
|
||||
with Invalid_argument _ -> 0
|
||||
in
|
||||
Scopelang.Ast.ELit
|
||||
(Dcalc.Ast.LRat
|
||||
(Q.div
|
||||
Q.(of_bigint i + (of_bigint f / of_bigint (Z.pow (Z.of_int 10) digits_f)))
|
||||
(Q.of_int 100)))
|
||||
| Bool b -> Scopelang.Ast.ELit (Dcalc.Ast.LBool b)
|
||||
| MoneyAmount i ->
|
||||
Scopelang.Ast.ELit
|
||||
(Dcalc.Ast.LMoney Z.((i.money_amount_units * of_int 100) + i.money_amount_cents))
|
||||
| Number ((Int i, _), Some (Year, _)) ->
|
||||
Scopelang.Ast.ELit (Dcalc.Ast.LDuration Z.(of_int 365 * i))
|
||||
| Number ((Int i, _), Some (Month, _)) ->
|
||||
Scopelang.Ast.ELit (Dcalc.Ast.LDuration Z.(of_int 30 * i))
|
||||
| Number ((Int i, _), Some (Day, _)) -> Scopelang.Ast.ELit (Dcalc.Ast.LDuration i)
|
||||
| Number ((Dec (_, _), _), Some ((Year | Month | Day), _)) ->
|
||||
Errors.raise_spanned_error
|
||||
"Impossible to specify decimal amounts of days, months or years" pos
|
||||
| Date date -> (
|
||||
let date =
|
||||
ODate.Unix.make
|
||||
~year:(Pos.unmark date.literal_date_year)
|
||||
~day:(Pos.unmark date.literal_date_day)
|
||||
~month:
|
||||
( try ODate.Month.of_int (Pos.unmark date.literal_date_month)
|
||||
with Failure _ ->
|
||||
Errors.raise_spanned_error "Invalid month (should be between 1 and 12)"
|
||||
(Pos.get_position date.literal_date_month) )
|
||||
()
|
||||
in
|
||||
match ODate.Unix.some_if_valid date with
|
||||
| Some date -> Scopelang.Ast.ELit (Dcalc.Ast.LDate date)
|
||||
| None -> Errors.raise_spanned_error "Invalid date" pos )
|
||||
in
|
||||
((untyped_term, pos), TDummy)
|
||||
Bindlib.box (untyped_term, pos)
|
||||
| Ident x -> (
|
||||
(* first we check whether this is a local var, then we resort to scope-wide variables *)
|
||||
match def_key with
|
||||
| Some def_key -> (
|
||||
let def_ctxt = Uid.ScopeDefMap.find def_key scope_ctxt.definitions in
|
||||
match Uid.IdentMap.find_opt x def_ctxt.var_idmap with
|
||||
| None -> (
|
||||
match Uid.IdentMap.find_opt x scope_ctxt.var_idmap with
|
||||
| Some uid -> ((EVar (NoPrefix, uid), pos), TDummy)
|
||||
| None ->
|
||||
Name_resolution.raise_unknown_identifier "for a local or scope-wide variable"
|
||||
(x, pos) )
|
||||
| Some uid -> ((ELocalVar uid, pos), TDummy) )
|
||||
match Desugared.Ast.IdentMap.find_opt x ctxt.local_var_idmap with
|
||||
| None -> (
|
||||
match Uid.IdentMap.find_opt x scope_ctxt.var_idmap with
|
||||
| Some uid -> ((EVar (NoPrefix, uid), pos), TDummy)
|
||||
| None -> Name_resolution.raise_unknown_identifier "for a scope-wide variable" (x, pos) )
|
||||
match Desugared.Ast.IdentMap.find_opt x scope_ctxt.var_idmap with
|
||||
| Some uid -> Bindlib.box (Scopelang.Ast.ELocation (ScopeVar (uid, pos)), pos)
|
||||
| None ->
|
||||
Name_resolution.raise_unknown_identifier "for a local or scope-wide variable" (x, pos)
|
||||
)
|
||||
| Some uid ->
|
||||
Scopelang.Ast.make_var (uid, pos) (* the whole box thing is to accomodate for this case *)
|
||||
)
|
||||
| Dotted (e, x) -> (
|
||||
(* For now we only accept dotted identifiers of the type y.x where y is a sub-scope *)
|
||||
match Pos.unmark e with
|
||||
| Ident y ->
|
||||
let subscope_uid : Uid.SubScope.t =
|
||||
| Ident y when Name_resolution.is_subscope_uid scope ctxt y ->
|
||||
(* In this case, y.x is a subscope variable *)
|
||||
let subscope_uid : Scopelang.Ast.SubScopeName.t =
|
||||
Name_resolution.get_subscope_uid scope ctxt (Pos.same_pos_as y e)
|
||||
in
|
||||
let subscope_real_uid : Uid.Scope.t =
|
||||
Uid.SubScopeMap.find subscope_uid scope_ctxt.sub_scopes
|
||||
let subscope_real_uid : Scopelang.Ast.ScopeName.t =
|
||||
Scopelang.Ast.SubScopeMap.find subscope_uid scope_ctxt.sub_scopes
|
||||
in
|
||||
let subscope_var_uid = Name_resolution.get_var_uid subscope_real_uid ctxt x in
|
||||
((EVar (SubScopePrefix subscope_uid, subscope_var_uid), pos), TDummy)
|
||||
Bindlib.box
|
||||
( Scopelang.Ast.ELocation
|
||||
(SubScopeVar (subscope_real_uid, (subscope_uid, pos), (subscope_var_uid, pos))),
|
||||
pos )
|
||||
| _ ->
|
||||
Name_resolution.raise_unsupported_feature
|
||||
"left hand side of a dotted expression should be an identifier" pos )
|
||||
| FunCall (f, arg) -> ((EApp (rec_helper f, [ rec_helper arg ]), pos), TDummy)
|
||||
| _ -> Name_resolution.raise_unsupported_feature "unsupported expression" pos
|
||||
(* In this case e.x is the struct field x access of expression e *)
|
||||
let e = translate_expr scope ctxt e in
|
||||
let x_possible_structs =
|
||||
try Desugared.Ast.IdentMap.find (Pos.unmark x) ctxt.field_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error "This identifier should refer to a struct field"
|
||||
(Pos.get_position x)
|
||||
in
|
||||
if Scopelang.Ast.StructMap.cardinal x_possible_structs > 1 then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"This struct field name is ambiguous, it can belong to %a. Desambiguate it by \
|
||||
prefixing it with the struct name."
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt " or ")
|
||||
(fun fmt (s_name, _) ->
|
||||
Format.fprintf fmt "%a" Scopelang.Ast.StructName.format_t s_name))
|
||||
(Scopelang.Ast.StructMap.bindings x_possible_structs))
|
||||
(Pos.get_position x)
|
||||
else
|
||||
let s_uid, f_uid = Scopelang.Ast.StructMap.choose x_possible_structs in
|
||||
Bindlib.box_apply (fun e -> (Scopelang.Ast.EStructAccess (e, f_uid, s_uid), pos)) e )
|
||||
| FunCall (f, arg) ->
|
||||
Bindlib.box_apply2
|
||||
(fun f arg -> (Scopelang.Ast.EApp (f, [ arg ]), pos))
|
||||
(rec_helper f) (rec_helper arg)
|
||||
| StructLit (s_name, fields) ->
|
||||
let s_uid =
|
||||
try Desugared.Ast.IdentMap.find (Pos.unmark s_name) ctxt.struct_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error "This identifier should refer to a struct name"
|
||||
(Pos.get_position s_name)
|
||||
in
|
||||
let s_fields =
|
||||
List.fold_left
|
||||
(fun s_fields (f_name, f_e) ->
|
||||
let f_uid =
|
||||
try
|
||||
Scopelang.Ast.StructMap.find s_uid
|
||||
(Desugared.Ast.IdentMap.find (Pos.unmark f_name) ctxt.field_idmap)
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "This identifier should refer to a field of struct %s"
|
||||
(Pos.unmark s_name))
|
||||
(Pos.get_position f_name)
|
||||
in
|
||||
( match Scopelang.Ast.StructFieldMap.find_opt f_uid s_fields with
|
||||
| None -> ()
|
||||
| Some e_field ->
|
||||
Errors.raise_multispanned_error
|
||||
(Format.asprintf "The field %a has been defined twice:"
|
||||
Scopelang.Ast.StructFieldName.format_t f_uid)
|
||||
[ (None, Pos.get_position f_e); (None, Pos.get_position (Bindlib.unbox e_field)) ]
|
||||
);
|
||||
let f_e = translate_expr scope ctxt f_e in
|
||||
Scopelang.Ast.StructFieldMap.add f_uid f_e s_fields)
|
||||
Scopelang.Ast.StructFieldMap.empty fields
|
||||
in
|
||||
Bindlib.box_apply
|
||||
(fun s_fields -> (Scopelang.Ast.EStruct (s_uid, s_fields), pos))
|
||||
(LiftStructFieldMap.lift_box s_fields)
|
||||
| EnumInject (constructor, payload) ->
|
||||
let possible_c_uids =
|
||||
try Desugared.Ast.IdentMap.find (Pos.unmark constructor) ctxt.constructor_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
"The name of this constructor has not been defined before, maybe it is a typo?"
|
||||
(Pos.get_position constructor)
|
||||
in
|
||||
if Scopelang.Ast.EnumMap.cardinal possible_c_uids > 1 then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"This constuctor name is ambiguous, it can belong to %a. Desambiguate it by prefixing \
|
||||
it with the enum name."
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt " or ")
|
||||
(fun fmt (s_name, _) ->
|
||||
Format.fprintf fmt "%a" Scopelang.Ast.EnumName.format_t s_name))
|
||||
(Scopelang.Ast.EnumMap.bindings possible_c_uids))
|
||||
(Pos.get_position constructor)
|
||||
else
|
||||
let e_uid, c_uid = Scopelang.Ast.EnumMap.choose possible_c_uids in
|
||||
let payload = Option.map (translate_expr scope ctxt) payload in
|
||||
Bindlib.box_apply
|
||||
(fun payload ->
|
||||
( Scopelang.Ast.EEnumInj
|
||||
( ( match payload with
|
||||
| Some e' -> e'
|
||||
| None -> (Scopelang.Ast.ELit Dcalc.Ast.LUnit, Pos.get_position constructor) ),
|
||||
c_uid,
|
||||
e_uid ),
|
||||
pos ))
|
||||
(Bindlib.box_opt payload)
|
||||
| MatchWith (e1, (cases, _cases_pos)) ->
|
||||
let e1 = translate_expr scope ctxt e1 in
|
||||
let cases_d, e_uid =
|
||||
List.fold_left
|
||||
(fun (cases_d, e_uid) (case, pos_case) ->
|
||||
match Pos.unmark case.Ast.match_case_pattern with
|
||||
| [ constructor ], binding ->
|
||||
let possible_c_uids =
|
||||
try Desugared.Ast.IdentMap.find (Pos.unmark constructor) ctxt.constructor_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
"The name of this constructor has not been defined before, maybe it is a \
|
||||
typo?"
|
||||
(Pos.get_position constructor)
|
||||
in
|
||||
if e_uid = None && Scopelang.Ast.EnumMap.cardinal possible_c_uids > 1 then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"This constuctor name is ambiguous, it can belong to %a. Desambiguate it by \
|
||||
prefixing it with the enum name."
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt " or ")
|
||||
(fun fmt (s_name, _) ->
|
||||
Format.fprintf fmt "%a" Scopelang.Ast.EnumName.format_t s_name))
|
||||
(Scopelang.Ast.EnumMap.bindings possible_c_uids))
|
||||
(Pos.get_position constructor)
|
||||
else
|
||||
let e_uid, c_uid =
|
||||
match e_uid with
|
||||
| Some e_uid -> (
|
||||
( e_uid,
|
||||
try Scopelang.Ast.EnumMap.find e_uid possible_c_uids
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "This constructor is not part of the %a enumeration"
|
||||
Scopelang.Ast.EnumName.format_t e_uid)
|
||||
(Pos.get_position constructor) ) )
|
||||
| None -> Scopelang.Ast.EnumMap.choose possible_c_uids
|
||||
in
|
||||
( match Scopelang.Ast.EnumConstructorMap.find_opt c_uid cases_d with
|
||||
| None -> ()
|
||||
| Some e_case ->
|
||||
Errors.raise_multispanned_error
|
||||
(Format.asprintf "The constructor %a has been matched twice:"
|
||||
Scopelang.Ast.EnumConstructor.format_t c_uid)
|
||||
[
|
||||
(None, Pos.get_position case.match_case_expr);
|
||||
(None, Pos.get_position (Bindlib.unbox e_case));
|
||||
] );
|
||||
let ctxt, (param_var, param_pos) =
|
||||
match binding with
|
||||
| None -> (ctxt, (Scopelang.Ast.Var.make ("_", Pos.no_pos), Pos.no_pos))
|
||||
| Some param ->
|
||||
let ctxt, param_var = Name_resolution.add_def_local_var ctxt param in
|
||||
(ctxt, (param_var, Pos.get_position param))
|
||||
in
|
||||
let case_body = translate_expr scope ctxt case.Ast.match_case_expr in
|
||||
let e_binder = Bindlib.bind_mvar (Array.of_list [ param_var ]) case_body in
|
||||
let case_expr =
|
||||
Bindlib.box_apply2
|
||||
(fun e_binder case_body ->
|
||||
Pos.same_pos_as
|
||||
(Scopelang.Ast.EAbs
|
||||
( param_pos,
|
||||
e_binder,
|
||||
[
|
||||
Scopelang.Ast.EnumConstructorMap.find c_uid
|
||||
(Scopelang.Ast.EnumMap.find e_uid ctxt.Name_resolution.enums);
|
||||
] ))
|
||||
case_body)
|
||||
e_binder case_body
|
||||
in
|
||||
(Scopelang.Ast.EnumConstructorMap.add c_uid case_expr cases_d, Some e_uid)
|
||||
| _ :: _, _ ->
|
||||
Errors.raise_spanned_error
|
||||
"The deep pattern matching syntactic sugar is not yet supported" pos_case
|
||||
| [], _ -> assert false
|
||||
(* should not happen *))
|
||||
(Scopelang.Ast.EnumConstructorMap.empty, None)
|
||||
cases
|
||||
in
|
||||
Bindlib.box_apply2
|
||||
(fun e1 cases_d -> (Scopelang.Ast.EMatch (e1, Option.get e_uid, cases_d), pos))
|
||||
e1
|
||||
(LiftEnumConstructorMap.lift_box cases_d)
|
||||
| ArrayLit es ->
|
||||
Bindlib.box_apply
|
||||
(fun es -> (Scopelang.Ast.EArray es, pos))
|
||||
(Bindlib.box_list (List.map rec_helper es))
|
||||
| CollectionOp (op', param', collection, predicate) ->
|
||||
let ctxt, param = Name_resolution.add_def_local_var ctxt param' in
|
||||
let collection = rec_helper collection in
|
||||
let init =
|
||||
match Pos.unmark op' with
|
||||
| Ast.Exists ->
|
||||
Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool false), Pos.get_position op')
|
||||
| Ast.Forall -> Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool true), Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Integer) ->
|
||||
Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LInt Z.zero), Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Decimal) ->
|
||||
Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LRat Q.zero), Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Money) ->
|
||||
Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LMoney Z.zero), Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateExtremum _) ->
|
||||
Errors.raise_spanned_error "Unsupported feature: minimum and maximum"
|
||||
(Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateSum t) ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "It is impossible to sum two values of type %a together"
|
||||
Print.format_primitive_typ t)
|
||||
pos
|
||||
| Ast.Aggregate Ast.AggregateCount ->
|
||||
Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LInt Z.zero), Pos.get_position op')
|
||||
in
|
||||
let acc_var = Scopelang.Ast.Var.make ("acc", Pos.get_position param') in
|
||||
let acc = Scopelang.Ast.make_var (acc_var, Pos.get_position param') in
|
||||
let f_body =
|
||||
let make_body (op : Dcalc.Ast.binop) =
|
||||
Bindlib.box_apply2
|
||||
(fun predicate acc ->
|
||||
( Scopelang.Ast.EApp
|
||||
( (Scopelang.Ast.EOp (Dcalc.Ast.Binop op), Pos.get_position op'),
|
||||
[ acc; predicate ] ),
|
||||
pos ))
|
||||
(translate_expr scope ctxt predicate)
|
||||
acc
|
||||
in
|
||||
match Pos.unmark op' with
|
||||
| Ast.Exists -> make_body Dcalc.Ast.Or
|
||||
| Ast.Forall -> make_body Dcalc.Ast.And
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Integer) -> make_body (Dcalc.Ast.Add Dcalc.Ast.KInt)
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Decimal) -> make_body (Dcalc.Ast.Add Dcalc.Ast.KRat)
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Money) -> make_body (Dcalc.Ast.Add Dcalc.Ast.KMoney)
|
||||
| Ast.Aggregate (Ast.AggregateSum _) -> assert false (* should not happen *)
|
||||
| Ast.Aggregate (Ast.AggregateExtremum _) ->
|
||||
Errors.raise_spanned_error "Unsupported feature: minimum and maximum"
|
||||
(Pos.get_position op')
|
||||
| Ast.Aggregate Ast.AggregateCount ->
|
||||
Bindlib.box_apply2
|
||||
(fun predicate acc ->
|
||||
( Scopelang.Ast.EIfThenElse
|
||||
( predicate,
|
||||
( Scopelang.Ast.EApp
|
||||
( ( Scopelang.Ast.EOp (Dcalc.Ast.Binop (Dcalc.Ast.Add Dcalc.Ast.KInt)),
|
||||
Pos.get_position op' ),
|
||||
[
|
||||
acc;
|
||||
(Scopelang.Ast.ELit (Dcalc.Ast.LInt Z.one), Pos.get_position predicate);
|
||||
] ),
|
||||
pos ),
|
||||
acc ),
|
||||
pos ))
|
||||
(translate_expr scope ctxt predicate)
|
||||
acc
|
||||
in
|
||||
let f =
|
||||
let make_f (t : Dcalc.Ast.typ_lit) =
|
||||
Bindlib.box_apply
|
||||
(fun binder ->
|
||||
( Scopelang.Ast.EAbs
|
||||
( pos,
|
||||
binder,
|
||||
[
|
||||
(Scopelang.Ast.TLit t, Pos.get_position op');
|
||||
(Scopelang.Ast.TAny, pos)
|
||||
(* we put any here because the type of the elements of the arrays is not
|
||||
always the type of the accumulator; for instance in AggregateCount. *);
|
||||
] ),
|
||||
pos ))
|
||||
(Bindlib.bind_mvar [| acc_var; param |] f_body)
|
||||
in
|
||||
match Pos.unmark op' with
|
||||
| Ast.Exists -> make_f Dcalc.Ast.TBool
|
||||
| Ast.Forall -> make_f Dcalc.Ast.TBool
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Integer) -> make_f Dcalc.Ast.TInt
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Decimal) -> make_f Dcalc.Ast.TRat
|
||||
| Ast.Aggregate (Ast.AggregateSum Ast.Money) -> make_f Dcalc.Ast.TMoney
|
||||
| Ast.Aggregate (Ast.AggregateExtremum _) ->
|
||||
Errors.raise_spanned_error "Unsupported feature: minimum and maximum"
|
||||
(Pos.get_position op')
|
||||
| Ast.Aggregate (Ast.AggregateSum _) -> assert false (* should not happen *)
|
||||
| Ast.Aggregate Ast.AggregateCount -> make_f Dcalc.Ast.TInt
|
||||
in
|
||||
Bindlib.box_apply3
|
||||
(fun f collection init ->
|
||||
( Scopelang.Ast.EApp
|
||||
((Scopelang.Ast.EOp (Dcalc.Ast.Ternop Dcalc.Ast.Fold), pos), [ f; init; collection ]),
|
||||
pos ))
|
||||
f collection init
|
||||
| MemCollection (member, collection) ->
|
||||
let param_var = Scopelang.Ast.Var.make ("collection_member", pos) in
|
||||
let param = Scopelang.Ast.make_var (param_var, pos) in
|
||||
let collection = rec_helper collection in
|
||||
let init = Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool false), pos) in
|
||||
let acc_var = Scopelang.Ast.Var.make ("acc", pos) in
|
||||
let acc = Scopelang.Ast.make_var (acc_var, pos) in
|
||||
let f_body =
|
||||
Bindlib.box_apply3
|
||||
(fun member acc param ->
|
||||
( Scopelang.Ast.EApp
|
||||
( (Scopelang.Ast.EOp (Dcalc.Ast.Binop Dcalc.Ast.Or), pos),
|
||||
[
|
||||
( Scopelang.Ast.EApp
|
||||
((Scopelang.Ast.EOp (Dcalc.Ast.Binop Dcalc.Ast.Eq), pos), [ member; param ]),
|
||||
pos );
|
||||
acc;
|
||||
] ),
|
||||
pos ))
|
||||
(translate_expr scope ctxt member)
|
||||
acc param
|
||||
in
|
||||
let f =
|
||||
Bindlib.box_apply
|
||||
(fun binder ->
|
||||
( Scopelang.Ast.EAbs
|
||||
( pos,
|
||||
binder,
|
||||
[ (Scopelang.Ast.TLit Dcalc.Ast.TBool, pos); (Scopelang.Ast.TAny, pos) ] ),
|
||||
pos ))
|
||||
(Bindlib.bind_mvar [| acc_var; param_var |] f_body)
|
||||
in
|
||||
Bindlib.box_apply3
|
||||
(fun f collection init ->
|
||||
( Scopelang.Ast.EApp
|
||||
((Scopelang.Ast.EOp (Dcalc.Ast.Ternop Dcalc.Ast.Fold), pos), [ f; init; collection ]),
|
||||
pos ))
|
||||
f collection init
|
||||
| Builtin IntToDec -> Bindlib.box (Scopelang.Ast.EOp (Dcalc.Ast.Unop Dcalc.Ast.IntToRat), pos)
|
||||
| Builtin Cardinal -> Bindlib.box (Scopelang.Ast.EOp (Dcalc.Ast.Unop Dcalc.Ast.Length), pos)
|
||||
| _ ->
|
||||
Name_resolution.raise_unsupported_feature "desugaring not implemented for this expression" pos
|
||||
|
||||
(* Translation from the parsed ast to the scope language *)
|
||||
(** {1 Translating scope definitions} *)
|
||||
|
||||
let merge_conditions (precond : Lambda_ast.term option) (cond : Lambda_ast.term option)
|
||||
(default_pos : Pos.t) : Lambda_ast.term =
|
||||
(** A scope use can be annotated with a pervasive precondition, in which case this precondition has
|
||||
to be appended to the justifications of each definition in the subscope use. This is what this
|
||||
function does. *)
|
||||
let merge_conditions (precond : Scopelang.Ast.expr Pos.marked Bindlib.box option)
|
||||
(cond : Scopelang.Ast.expr Pos.marked Bindlib.box option) (default_pos : Pos.t) :
|
||||
Scopelang.Ast.expr Pos.marked Bindlib.box =
|
||||
match (precond, cond) with
|
||||
| Some precond, Some cond ->
|
||||
let op_term = ((EOp (Binop And), Pos.get_position (fst precond)), TDummy) in
|
||||
((EApp (op_term, [ precond; cond ]), Pos.get_position (fst precond)), TDummy)
|
||||
let op_term =
|
||||
(Scopelang.Ast.EOp (Dcalc.Ast.Binop Dcalc.Ast.And), Pos.get_position (Bindlib.unbox precond))
|
||||
in
|
||||
Bindlib.box_apply2
|
||||
(fun precond cond ->
|
||||
(Scopelang.Ast.EApp (op_term, [ precond; cond ]), Pos.get_position precond))
|
||||
precond cond
|
||||
| Some cond, None | None, Some cond -> cond
|
||||
| None, None -> ((EBool true, default_pos), TBool)
|
||||
| None, None -> Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool true), default_pos)
|
||||
|
||||
let process_default (ctxt : Name_resolution.context) (scope : Uid.Scope.t)
|
||||
(def_key : Uid.ScopeDef.t) (def : Lambda_ast.default_term) (param_uid : Uid.LocalVar.t option)
|
||||
(precond : Lambda_ast.term option) (just : Catala_ast.expression Pos.marked option)
|
||||
(body : Catala_ast.expression Pos.marked) : Lambda_ast.default_term =
|
||||
let just =
|
||||
match just with
|
||||
| Some cond -> Some (expr_to_lambda scope (Some def_key) ctxt cond)
|
||||
| None -> None
|
||||
in
|
||||
let condition = merge_conditions precond just (Pos.get_position body) in
|
||||
let body = expr_to_lambda scope (Some def_key) ctxt body in
|
||||
(* if there's a parameter, we have to wrap the justifiction and the body in a func *)
|
||||
let condition, body =
|
||||
match param_uid with
|
||||
| None -> (condition, body)
|
||||
| Some param_uid ->
|
||||
( ((EFun ([ (param_uid, TDummy) ], condition), Pos.get_position (fst condition)), TDummy),
|
||||
((EFun ([ (param_uid, TDummy) ], body), Pos.get_position (fst body)), TDummy) )
|
||||
in
|
||||
Lambda_ast.add_default condition body def
|
||||
(** Translates a surface definition into condition into a desugared {!type: Desugared.Ast.rule} *)
|
||||
let process_default (ctxt : Name_resolution.context) (scope : Scopelang.Ast.ScopeName.t)
|
||||
(def_key : Desugared.Ast.ScopeDef.t Pos.marked)
|
||||
(param_uid : Scopelang.Ast.Var.t Pos.marked option)
|
||||
(precond : Scopelang.Ast.expr Pos.marked Bindlib.box option)
|
||||
(exception_to_rule : Desugared.Ast.RuleName.t option) (just : Ast.expression Pos.marked option)
|
||||
(cons : Ast.expression Pos.marked) : Desugared.Ast.rule =
|
||||
let just = match just with Some just -> Some (translate_expr scope ctxt just) | None -> None in
|
||||
let just = merge_conditions precond just (Pos.get_position def_key) in
|
||||
let cons = translate_expr scope ctxt cons in
|
||||
{
|
||||
just;
|
||||
cons;
|
||||
parameter =
|
||||
(let def_key_typ = Name_resolution.get_def_typ ctxt (Pos.unmark def_key) in
|
||||
match (Pos.unmark def_key_typ, param_uid) with
|
||||
| Scopelang.Ast.TArrow (t_in, _), Some param_uid -> Some (Pos.unmark param_uid, t_in)
|
||||
| Scopelang.Ast.TArrow _, None ->
|
||||
Errors.raise_spanned_error
|
||||
"This definition has a function type but the parameter is missing"
|
||||
(Pos.get_position (Bindlib.unbox cons))
|
||||
| _, Some _ ->
|
||||
Errors.raise_spanned_error
|
||||
"This definition has a parameter but its type is not a function"
|
||||
(Pos.get_position (Bindlib.unbox cons))
|
||||
| _ -> None);
|
||||
exception_to_rule;
|
||||
}
|
||||
|
||||
(* Process a definition *)
|
||||
let process_def (precond : Lambda_ast.term option) (scope_uid : Uid.Scope.t)
|
||||
(ctxt : Name_resolution.context) (prgm : Scope_ast.program) (def : Catala_ast.definition) :
|
||||
Scope_ast.program =
|
||||
let scope : Scope_ast.scope = Uid.ScopeMap.find scope_uid prgm in
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
(** Wrapper around {!val: process_default} that performs some name disambiguation *)
|
||||
let process_def (precond : Scopelang.Ast.expr Pos.marked Bindlib.box option)
|
||||
(scope_uid : Scopelang.Ast.ScopeName.t) (ctxt : Name_resolution.context)
|
||||
(prgm : Desugared.Ast.program) (def : Ast.definition) : Desugared.Ast.program =
|
||||
let scope : Desugared.Ast.scope = Scopelang.Ast.ScopeMap.find scope_uid prgm.program_scopes in
|
||||
let scope_ctxt = Scopelang.Ast.ScopeMap.find scope_uid ctxt.scopes in
|
||||
let default_pos = Pos.get_position def.definition_expr in
|
||||
let param_uid (def_uid : Uid.ScopeDef.t) : Uid.LocalVar.t option =
|
||||
match def.definition_parameter with
|
||||
| None -> None
|
||||
| Some param ->
|
||||
let def_ctxt = Uid.ScopeDefMap.find def_uid scope_ctxt.definitions in
|
||||
Some (Uid.IdentMap.find (Pos.unmark param) def_ctxt.var_idmap)
|
||||
in
|
||||
let def_key =
|
||||
match Pos.unmark def.definition_name with
|
||||
| [ x ] ->
|
||||
let x_uid = Name_resolution.get_var_uid scope_uid ctxt x in
|
||||
Uid.ScopeDef.Var x_uid
|
||||
Desugared.Ast.ScopeDef.Var x_uid
|
||||
| [ y; x ] ->
|
||||
let subscope_uid : Uid.SubScope.t = Name_resolution.get_subscope_uid scope_uid ctxt y in
|
||||
let subscope_real_uid : Uid.Scope.t =
|
||||
Uid.SubScopeMap.find subscope_uid scope_ctxt.sub_scopes
|
||||
let subscope_uid : Scopelang.Ast.SubScopeName.t =
|
||||
Name_resolution.get_subscope_uid scope_uid ctxt y
|
||||
in
|
||||
let subscope_real_uid : Scopelang.Ast.ScopeName.t =
|
||||
Scopelang.Ast.SubScopeMap.find subscope_uid scope_ctxt.sub_scopes
|
||||
in
|
||||
let x_uid = Name_resolution.get_var_uid subscope_real_uid ctxt x in
|
||||
Uid.ScopeDef.SubScopeVar (subscope_uid, x_uid)
|
||||
Desugared.Ast.ScopeDef.SubScopeVar (subscope_uid, x_uid)
|
||||
| _ -> Errors.raise_spanned_error "Structs are not handled yet" default_pos
|
||||
in
|
||||
(* We add to the name resolution context the name of the parameter variable *)
|
||||
let param_uid, new_ctxt =
|
||||
match def.definition_parameter with
|
||||
| None -> (None, ctxt)
|
||||
| Some param ->
|
||||
let ctxt, param_var = Name_resolution.add_def_local_var ctxt param in
|
||||
(Some (Pos.same_pos_as param_var param), ctxt)
|
||||
in
|
||||
let scope_updated =
|
||||
let x_def =
|
||||
match Uid.ScopeDefMap.find_opt def_key scope.scope_defs with
|
||||
let x_def, x_type, is_cond =
|
||||
match Desugared.Ast.ScopeDefMap.find_opt def_key scope.scope_defs with
|
||||
| Some def -> def
|
||||
| None ->
|
||||
let typ = Name_resolution.get_def_typ ctxt def_key in
|
||||
Scope_ast.empty_def default_pos typ
|
||||
( Desugared.Ast.RuleMap.empty,
|
||||
Name_resolution.get_def_typ ctxt def_key,
|
||||
Name_resolution.is_def_cond ctxt def_key )
|
||||
in
|
||||
let rule_name =
|
||||
match def.Ast.definition_label with
|
||||
| None -> None
|
||||
| Some label -> Some (Desugared.Ast.IdentMap.find (Pos.unmark label) scope_ctxt.label_idmap)
|
||||
in
|
||||
let rule_name =
|
||||
match rule_name with
|
||||
| Some x -> x
|
||||
| None ->
|
||||
Desugared.Ast.RuleName.fresh
|
||||
(Pos.map_under_mark
|
||||
(fun qident -> String.concat "." (List.map (fun i -> Pos.unmark i) qident))
|
||||
def.definition_name)
|
||||
in
|
||||
let parent_rule =
|
||||
match def.Ast.definition_exception_to with
|
||||
| None -> None
|
||||
| Some label ->
|
||||
Some
|
||||
( try Desugared.Ast.IdentMap.find (Pos.unmark label) scope_ctxt.label_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "Unknown label: \"%s\"" (Pos.unmark label))
|
||||
(Pos.get_position label) )
|
||||
in
|
||||
let x_def =
|
||||
Lambda_ast.map_untype
|
||||
(fun t ->
|
||||
match t with
|
||||
| EDefault default ->
|
||||
EDefault
|
||||
(process_default ctxt scope_uid def_key default (param_uid def_key) precond
|
||||
def.definition_condition def.definition_expr)
|
||||
| _ -> assert false
|
||||
(* should not happen *))
|
||||
Desugared.Ast.RuleMap.add rule_name
|
||||
(process_default new_ctxt scope_uid
|
||||
(def_key, Pos.get_position def.definition_name)
|
||||
param_uid precond parent_rule def.definition_condition def.definition_expr)
|
||||
x_def
|
||||
in
|
||||
{ scope with scope_defs = Uid.ScopeDefMap.add def_key x_def scope.scope_defs }
|
||||
{
|
||||
scope with
|
||||
scope_defs = Desugared.Ast.ScopeDefMap.add def_key (x_def, x_type, is_cond) scope.scope_defs;
|
||||
}
|
||||
in
|
||||
Uid.ScopeMap.add scope_uid scope_updated prgm
|
||||
{
|
||||
prgm with
|
||||
program_scopes = Scopelang.Ast.ScopeMap.add scope_uid scope_updated prgm.program_scopes;
|
||||
}
|
||||
|
||||
(** Process a rule from the surface language *)
|
||||
let process_rule (precond : Lambda_ast.term option) (scope : Uid.Scope.t)
|
||||
(ctxt : Name_resolution.context) (prgm : Scope_ast.program) (rule : Catala_ast.rule) :
|
||||
Scope_ast.program =
|
||||
let consequence_expr = Catala_ast.Literal (Catala_ast.Bool (Pos.unmark rule.rule_consequence)) in
|
||||
(** Translates a {!type: Surface.Ast.rule} from the surface language *)
|
||||
let process_rule (precond : Scopelang.Ast.expr Pos.marked Bindlib.box option)
|
||||
(scope : Scopelang.Ast.ScopeName.t) (ctxt : Name_resolution.context)
|
||||
(prgm : Desugared.Ast.program) (rule : Ast.rule) : Desugared.Ast.program =
|
||||
let consequence_expr = Ast.Literal (Ast.Bool (Pos.unmark rule.rule_consequence)) in
|
||||
let def =
|
||||
{
|
||||
definition_name = rule.rule_name;
|
||||
definition_parameter = rule.rule_parameter;
|
||||
definition_condition = rule.rule_condition;
|
||||
definition_expr = (consequence_expr, Pos.get_position rule.rule_consequence);
|
||||
Ast.definition_label = rule.rule_label;
|
||||
Ast.definition_exception_to = rule.rule_exception_to;
|
||||
Ast.definition_name = rule.rule_name;
|
||||
Ast.definition_parameter = rule.rule_parameter;
|
||||
Ast.definition_condition = rule.rule_condition;
|
||||
Ast.definition_expr = (consequence_expr, Pos.get_position rule.rule_consequence);
|
||||
}
|
||||
in
|
||||
process_def precond scope ctxt prgm def
|
||||
|
||||
let process_scope_use_item (cond : Lambda_ast.term option) (scope : Uid.Scope.t)
|
||||
(ctxt : Name_resolution.context) (prgm : Scope_ast.program)
|
||||
(item : Catala_ast.scope_use_item Pos.marked) : Scope_ast.program =
|
||||
(** Translates assertions *)
|
||||
let process_assert (precond : Scopelang.Ast.expr Pos.marked Bindlib.box option)
|
||||
(scope_uid : Scopelang.Ast.ScopeName.t) (ctxt : Name_resolution.context)
|
||||
(prgm : Desugared.Ast.program) (ass : Ast.assertion) : Desugared.Ast.program =
|
||||
let scope : Desugared.Ast.scope = Scopelang.Ast.ScopeMap.find scope_uid prgm.program_scopes in
|
||||
let ass =
|
||||
translate_expr scope_uid ctxt
|
||||
( match ass.Ast.assertion_condition with
|
||||
| None -> ass.Ast.assertion_content
|
||||
| Some cond ->
|
||||
( Ast.IfThenElse
|
||||
(cond, ass.Ast.assertion_content, Pos.same_pos_as (Ast.Literal (Ast.Bool true)) cond),
|
||||
Pos.get_position cond ) )
|
||||
in
|
||||
let ass =
|
||||
match precond with
|
||||
| Some precond ->
|
||||
Bindlib.box_apply2
|
||||
(fun precond ass ->
|
||||
( Scopelang.Ast.EIfThenElse
|
||||
(precond, ass, Pos.same_pos_as (Scopelang.Ast.ELit (Dcalc.Ast.LBool true)) precond),
|
||||
Pos.get_position precond ))
|
||||
precond ass
|
||||
| None -> ass
|
||||
in
|
||||
let new_scope = { scope with scope_assertions = ass :: scope.scope_assertions } in
|
||||
{ prgm with program_scopes = Scopelang.Ast.ScopeMap.add scope_uid new_scope prgm.program_scopes }
|
||||
|
||||
(** Translates a surface definition, rule or assertion *)
|
||||
let process_scope_use_item (precond : Ast.expression Pos.marked option)
|
||||
(scope : Scopelang.Ast.ScopeName.t) (ctxt : Name_resolution.context)
|
||||
(prgm : Desugared.Ast.program) (item : Ast.scope_use_item Pos.marked) : Desugared.Ast.program =
|
||||
let precond = Option.map (translate_expr scope ctxt) precond in
|
||||
match Pos.unmark item with
|
||||
| Catala_ast.Rule rule -> process_rule cond scope ctxt prgm rule
|
||||
| Catala_ast.Definition def -> process_def cond scope ctxt prgm def
|
||||
| Ast.Rule rule -> process_rule precond scope ctxt prgm rule
|
||||
| Ast.Definition def -> process_def precond scope ctxt prgm def
|
||||
| Ast.Assertion ass -> process_assert precond scope ctxt prgm ass
|
||||
| _ -> prgm
|
||||
|
||||
let process_scope_use (ctxt : Name_resolution.context) (prgm : Scope_ast.program)
|
||||
(use : Catala_ast.scope_use) : Scope_ast.program =
|
||||
(** {1 Translating top-level items} *)
|
||||
|
||||
(** Translates a surface scope use, which is a bunch of definitions *)
|
||||
let process_scope_use (ctxt : Name_resolution.context) (prgm : Desugared.Ast.program)
|
||||
(use : Ast.scope_use) : Desugared.Ast.program =
|
||||
let name = fst use.scope_use_name in
|
||||
let scope_uid = Uid.IdentMap.find name ctxt.scope_idmap in
|
||||
let scope_uid = Desugared.Ast.IdentMap.find name ctxt.scope_idmap in
|
||||
(* Make sure the scope exists *)
|
||||
let prgm =
|
||||
match Uid.ScopeMap.find_opt scope_uid prgm with
|
||||
match Scopelang.Ast.ScopeMap.find_opt scope_uid prgm.program_scopes with
|
||||
| Some _ -> prgm
|
||||
| None -> Uid.ScopeMap.add scope_uid (Scope_ast.empty_scope scope_uid) prgm
|
||||
| None -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
let cond =
|
||||
match use.scope_use_condition with
|
||||
| Some expr ->
|
||||
let untyped_term = expr_to_lambda scope_uid None ctxt expr in
|
||||
Some untyped_term
|
||||
| None -> None
|
||||
in
|
||||
List.fold_left (process_scope_use_item cond scope_uid ctxt) prgm use.scope_use_items
|
||||
let precond = use.scope_use_condition in
|
||||
List.fold_left (process_scope_use_item precond scope_uid ctxt) prgm use.scope_use_items
|
||||
|
||||
(** Scopes processing *)
|
||||
let translate_program_to_scope (ctxt : Name_resolution.context) (prgm : Catala_ast.program) :
|
||||
Scope_ast.program =
|
||||
let empty_prgm = Uid.ScopeMap.empty in
|
||||
let processer_article_item (prgm : Scope_ast.program) (item : Catala_ast.law_article_item) :
|
||||
Scope_ast.program =
|
||||
(** Main function of this module *)
|
||||
let desugar_program (ctxt : Name_resolution.context) (prgm : Ast.program) : Desugared.Ast.program =
|
||||
let empty_prgm =
|
||||
{
|
||||
Desugared.Ast.program_structs =
|
||||
Scopelang.Ast.StructMap.map Scopelang.Ast.StructFieldMap.bindings
|
||||
ctxt.Name_resolution.structs;
|
||||
Desugared.Ast.program_enums =
|
||||
Scopelang.Ast.EnumMap.map Scopelang.Ast.EnumConstructorMap.bindings
|
||||
ctxt.Name_resolution.enums;
|
||||
Desugared.Ast.program_scopes =
|
||||
Scopelang.Ast.ScopeMap.mapi
|
||||
(fun s_uid s_context ->
|
||||
{
|
||||
Desugared.Ast.scope_vars =
|
||||
Desugared.Ast.IdentMap.fold
|
||||
(fun _ v acc -> Scopelang.Ast.ScopeVarSet.add v acc)
|
||||
s_context.Name_resolution.var_idmap Scopelang.Ast.ScopeVarSet.empty;
|
||||
Desugared.Ast.scope_sub_scopes = s_context.Name_resolution.sub_scopes;
|
||||
Desugared.Ast.scope_defs =
|
||||
Desugared.Ast.IdentMap.fold
|
||||
(fun _ v acc ->
|
||||
let x, y = Scopelang.Ast.ScopeVarMap.find v ctxt.Name_resolution.var_typs in
|
||||
Desugared.Ast.ScopeDefMap.add (Desugared.Ast.ScopeDef.Var v)
|
||||
(Desugared.Ast.RuleMap.empty, x, y)
|
||||
acc)
|
||||
s_context.Name_resolution.var_idmap Desugared.Ast.ScopeDefMap.empty;
|
||||
Desugared.Ast.scope_assertions = [];
|
||||
Desugared.Ast.scope_meta_assertions = [];
|
||||
Desugared.Ast.scope_uid = s_uid;
|
||||
})
|
||||
ctxt.Name_resolution.scopes;
|
||||
}
|
||||
in
|
||||
let processer_article_item (prgm : Desugared.Ast.program) (item : Ast.law_article_item) :
|
||||
Desugared.Ast.program =
|
||||
match item with
|
||||
| CodeBlock (block, _) ->
|
||||
List.fold_left
|
||||
(fun prgm item ->
|
||||
match Pos.unmark item with ScopeUse use -> process_scope_use ctxt prgm use | _ -> prgm)
|
||||
match Pos.unmark item with
|
||||
| Ast.ScopeUse use -> process_scope_use ctxt prgm use
|
||||
| _ -> prgm)
|
||||
prgm block
|
||||
| _ -> prgm
|
||||
in
|
||||
let rec processer_structure (prgm : Scope_ast.program) (item : Catala_ast.law_structure) :
|
||||
Scope_ast.program =
|
||||
let rec processer_structure (prgm : Desugared.Ast.program) (item : Ast.law_structure) :
|
||||
Desugared.Ast.program =
|
||||
match item with
|
||||
| LawHeading (_, children) ->
|
||||
List.fold_left (fun prgm child -> processer_structure prgm child) prgm children
|
||||
| LawArticle (_, children) ->
|
||||
List.fold_left (fun prgm child -> processer_article_item prgm child) prgm children
|
||||
| MetadataBlock (b, c) -> processer_article_item prgm (CodeBlock (b, c))
|
||||
| IntermediateText _ -> prgm
|
||||
| IntermediateText _ | LawInclude _ -> prgm
|
||||
in
|
||||
|
||||
let processer_item (prgm : Scope_ast.program) (item : Catala_ast.program_item) : Scope_ast.program
|
||||
=
|
||||
let processer_item (prgm : Desugared.Ast.program) (item : Ast.program_item) :
|
||||
Desugared.Ast.program =
|
||||
match item with LawStructure s -> processer_structure prgm s
|
||||
in
|
||||
|
||||
|
@ -1,3 +1,15 @@
|
||||
(library
|
||||
(name surface)
|
||||
(libraries utils menhirLib sedlex re desugared scopelang zarith
|
||||
zarith_stubs_js odate)
|
||||
(public_name catala.surface)
|
||||
(preprocess
|
||||
(pps sedlex.ppx)))
|
||||
|
||||
(menhir
|
||||
(modules parser)
|
||||
(flags --table))
|
||||
|
||||
(documentation
|
||||
(package catala)
|
||||
(mld_files surface))
|
||||
|
@ -12,19 +12,34 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Concise syntax with English abbreviated keywords. *)
|
||||
|
||||
open Parser
|
||||
open Sedlexing
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module R = Re.Pcre
|
||||
|
||||
(** Boolean reference, used by the lexer as the mutable state to distinguish whether it is lexing
|
||||
code or law. *)
|
||||
let is_code : bool ref = ref false
|
||||
|
||||
(** Mutable string reference that accumulates the string representation of the body of code being
|
||||
lexed. This string representation is used in the literate programming backends to faithfully
|
||||
capture the spacing pattern of the original program *)
|
||||
let code_string_acc : string ref = ref ""
|
||||
|
||||
(** Updates {!val:code_string_acc} with the current lexeme *)
|
||||
let update_acc (lexbuf : lexbuf) : unit = code_string_acc := !code_string_acc ^ Utf8.lexeme lexbuf
|
||||
|
||||
let raise_lexer_error (loc : Pos.t) (token : string) (msg : string) =
|
||||
Errors.raise_spanned_error (Printf.sprintf "Parsing error on token \"%s\": %s" token msg) loc
|
||||
(** Error-generating helper *)
|
||||
let raise_lexer_error (loc : Pos.t) (token : string) =
|
||||
Errors.raise_spanned_error
|
||||
(Printf.sprintf "Parsing error after token \"%s\": what comes after is unknown" token)
|
||||
loc
|
||||
|
||||
(** Associative list matching each punctuation string part of the Catala syntax with its {!module:
|
||||
Surface.Parser} token. Same for all the input languages (English, French, etc.) *)
|
||||
let token_list_language_agnostic : (string * token) list =
|
||||
[
|
||||
("->", ARROW);
|
||||
@ -36,47 +51,59 @@ let token_list_language_agnostic : (string * token) list =
|
||||
("=", EQUAL);
|
||||
("(", LPAREN);
|
||||
(")", RPAREN);
|
||||
("{", LBRACKET);
|
||||
("}", RBRACKET);
|
||||
("{", LSQUARE);
|
||||
("}", RSQUARE);
|
||||
("+", PLUS);
|
||||
("-", MINUS);
|
||||
("*", MULT);
|
||||
("/", DIV);
|
||||
("|", VERTICAL);
|
||||
(":", COLON);
|
||||
(";", SEMICOLON);
|
||||
("--", ALT);
|
||||
]
|
||||
|
||||
(** Same as {!val: token_list_language_agnostic}, but with tokens whose string varies with the input
|
||||
language. *)
|
||||
let token_list : (string * token) list =
|
||||
[
|
||||
("scope", SCOPE);
|
||||
("]", CONSEQUENCE);
|
||||
("|]", CONSEQUENCE);
|
||||
("data", DATA);
|
||||
("fun of", DEPENDS);
|
||||
("new", DECLARATION);
|
||||
("param", CONTEXT);
|
||||
("decreasing", DECREASING);
|
||||
("increasing", INCREASING);
|
||||
("int_to_dec", INT_TO_DEC);
|
||||
("maximum", MAXIMUM);
|
||||
("minimum", MAXIMUM);
|
||||
("of", OF);
|
||||
("set", COLLECTION);
|
||||
("enum", ENUM);
|
||||
("int", INTEGER);
|
||||
("amount", MONEY);
|
||||
("money", MONEY);
|
||||
("text", TEXT);
|
||||
("decimal", DECIMAL);
|
||||
("date", DATE);
|
||||
("duration", DURATION);
|
||||
("boolean", BOOLEAN);
|
||||
("sum", SUM);
|
||||
("ok", FILLED);
|
||||
("def", DEFINITION);
|
||||
("label", LABEL);
|
||||
("exception", EXCEPTION);
|
||||
("equals", DEFINED_AS);
|
||||
("match", MATCH);
|
||||
("with", WITH);
|
||||
("[", UNDER_CONDITION);
|
||||
("[|", UNDER_CONDITION);
|
||||
("if", IF);
|
||||
("then", THEN);
|
||||
("else", ELSE);
|
||||
("type", CONTENT);
|
||||
("content", CONTENT);
|
||||
("struct", STRUCT);
|
||||
("option", OPTIONAL);
|
||||
("assert", ASSERTION);
|
||||
("varies", VARIES);
|
||||
("with parameter", WITH_V);
|
||||
@ -89,18 +116,22 @@ let token_list : (string * token) list =
|
||||
("exists", EXISTS);
|
||||
("such", SUCH);
|
||||
("that", THAT);
|
||||
("now", NOW);
|
||||
("&&", AND);
|
||||
("||", OR);
|
||||
("not", NOT);
|
||||
("number", CARDINAL);
|
||||
("year", YEAR);
|
||||
("month", MONTH);
|
||||
("day", DAY);
|
||||
("true", TRUE);
|
||||
("false", FALSE);
|
||||
]
|
||||
@ token_list_language_agnostic
|
||||
|
||||
(** Main lexing function used in a code block *)
|
||||
let rec lex_code (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| white_space ->
|
||||
(* Whitespaces *)
|
||||
@ -147,7 +178,7 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "int" ->
|
||||
update_acc lexbuf;
|
||||
INTEGER
|
||||
| "amount" ->
|
||||
| "money" ->
|
||||
update_acc lexbuf;
|
||||
MONEY
|
||||
| "text" ->
|
||||
@ -159,6 +190,9 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "date" ->
|
||||
update_acc lexbuf;
|
||||
DATE
|
||||
| "duration" ->
|
||||
update_acc lexbuf;
|
||||
DURATION
|
||||
| "bool" ->
|
||||
update_acc lexbuf;
|
||||
BOOLEAN
|
||||
@ -171,13 +205,19 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "def" ->
|
||||
update_acc lexbuf;
|
||||
DEFINITION
|
||||
| "label" ->
|
||||
update_acc lexbuf;
|
||||
LABEL
|
||||
| "exception" ->
|
||||
update_acc lexbuf;
|
||||
EXCEPTION
|
||||
| ":=" ->
|
||||
update_acc lexbuf;
|
||||
DEFINED_AS
|
||||
| "varies" ->
|
||||
update_acc lexbuf;
|
||||
VARIES
|
||||
| "with" ->
|
||||
| "withv" ->
|
||||
update_acc lexbuf;
|
||||
WITH_V
|
||||
| "match" ->
|
||||
@ -186,7 +226,7 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "with" ->
|
||||
update_acc lexbuf;
|
||||
WITH
|
||||
| "[" ->
|
||||
| "[|" ->
|
||||
update_acc lexbuf;
|
||||
UNDER_CONDITION
|
||||
| "if" ->
|
||||
@ -201,15 +241,12 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "condition" ->
|
||||
update_acc lexbuf;
|
||||
CONDITION
|
||||
| "type" ->
|
||||
| "content" ->
|
||||
update_acc lexbuf;
|
||||
CONTENT
|
||||
| "structure" ->
|
||||
| "struct" ->
|
||||
update_acc lexbuf;
|
||||
STRUCT
|
||||
| "option" ->
|
||||
update_acc lexbuf;
|
||||
OPTIONAL
|
||||
| "assert" ->
|
||||
update_acc lexbuf;
|
||||
ASSERTION
|
||||
@ -244,9 +281,6 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "that" ->
|
||||
update_acc lexbuf;
|
||||
THAT
|
||||
| "now" ->
|
||||
update_acc lexbuf;
|
||||
NOW
|
||||
| "&&" ->
|
||||
update_acc lexbuf;
|
||||
AND
|
||||
@ -256,9 +290,18 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "not" ->
|
||||
update_acc lexbuf;
|
||||
NOT
|
||||
| "]" ->
|
||||
| "|]" ->
|
||||
update_acc lexbuf;
|
||||
CONSEQUENCE
|
||||
| "int_to_dec" ->
|
||||
update_acc lexbuf;
|
||||
INT_TO_DEC
|
||||
| "maximum" ->
|
||||
update_acc lexbuf;
|
||||
MAXIMUM
|
||||
| "minimum" ->
|
||||
update_acc lexbuf;
|
||||
MINIMUM
|
||||
| "number" ->
|
||||
update_acc lexbuf;
|
||||
CARDINAL
|
||||
@ -271,6 +314,12 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| "year" ->
|
||||
update_acc lexbuf;
|
||||
YEAR
|
||||
| "month" ->
|
||||
update_acc lexbuf;
|
||||
MONTH
|
||||
| "day" ->
|
||||
update_acc lexbuf;
|
||||
DAY
|
||||
| 0x24, Star white_space, '0' .. '9', Star ('0' .. '9' | ','), Opt ('.', Rep ('0' .. '9', 0 .. 2))
|
||||
->
|
||||
let extract_parts = R.regexp "([0-9]([0-9,]*[0-9]|))(.([0-9]{0,2})|)" in
|
||||
@ -280,8 +329,8 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
(* Integer literal*)
|
||||
let units = parts 1 in
|
||||
let remove_commas = R.regexp "," in
|
||||
let units = int_of_string (R.substitute ~rex:remove_commas ~subst:(fun _ -> "") units) in
|
||||
let cents = try int_of_string (parts 4) with Not_found -> 0 in
|
||||
let units = Z.of_string (R.substitute ~rex:remove_commas ~subst:(fun _ -> "") units) in
|
||||
let cents = try Z.of_string (parts 4) with Not_found -> Z.zero in
|
||||
update_acc lexbuf;
|
||||
MONEY_AMOUNT (units, cents)
|
||||
| Plus '0' .. '9', '.', Star '0' .. '9' ->
|
||||
@ -289,13 +338,94 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
let dec_parts = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
(* Integer literal*)
|
||||
update_acc lexbuf;
|
||||
DECIMAL_LITERAL (int_of_string (dec_parts 1), int_of_string (dec_parts 2))
|
||||
DECIMAL_LITERAL (Z.of_string (dec_parts 1), Z.of_string (dec_parts 2))
|
||||
| "->" ->
|
||||
update_acc lexbuf;
|
||||
ARROW
|
||||
| '.' ->
|
||||
| "<=@" ->
|
||||
update_acc lexbuf;
|
||||
DOT
|
||||
LESSER_EQUAL_DATE
|
||||
| "<@" ->
|
||||
update_acc lexbuf;
|
||||
LESSER_DATE
|
||||
| ">=@" ->
|
||||
update_acc lexbuf;
|
||||
GREATER_EQUAL_DATE
|
||||
| ">@" ->
|
||||
update_acc lexbuf;
|
||||
GREATER_DATE
|
||||
| "-@" ->
|
||||
update_acc lexbuf;
|
||||
MINUSDATE
|
||||
| "+@" ->
|
||||
update_acc lexbuf;
|
||||
PLUSDATE
|
||||
| "<=^" ->
|
||||
update_acc lexbuf;
|
||||
LESSER_EQUAL_DURATION
|
||||
| "<^" ->
|
||||
update_acc lexbuf;
|
||||
LESSER_DURATION
|
||||
| ">=^" ->
|
||||
update_acc lexbuf;
|
||||
GREATER_EQUAL_DURATION
|
||||
| ">^" ->
|
||||
update_acc lexbuf;
|
||||
GREATER_DURATION
|
||||
| "+^" ->
|
||||
update_acc lexbuf;
|
||||
PLUSDURATION
|
||||
| "-^" ->
|
||||
update_acc lexbuf;
|
||||
MINUSDURATION
|
||||
| "<=", 0x24 ->
|
||||
update_acc lexbuf;
|
||||
LESSER_EQUAL_MONEY
|
||||
| '<', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
LESSER_MONEY
|
||||
| ">=", 0x24 ->
|
||||
update_acc lexbuf;
|
||||
GREATER_EQUAL_MONEY
|
||||
| '>', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
GREATER_MONEY
|
||||
| '+', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
PLUSMONEY
|
||||
| '-', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
MINUSMONEY
|
||||
| '*', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
MULTMONEY
|
||||
| '/', 0x24 ->
|
||||
update_acc lexbuf;
|
||||
DIVMONEY
|
||||
| "<=." ->
|
||||
update_acc lexbuf;
|
||||
LESSER_EQUAL_DEC
|
||||
| "<." ->
|
||||
update_acc lexbuf;
|
||||
LESSER_DEC
|
||||
| ">=." ->
|
||||
update_acc lexbuf;
|
||||
GREATER_EQUAL_DEC
|
||||
| ">." ->
|
||||
update_acc lexbuf;
|
||||
GREATER_DEC
|
||||
| "+." ->
|
||||
update_acc lexbuf;
|
||||
PLUSDEC
|
||||
| "-." ->
|
||||
update_acc lexbuf;
|
||||
MINUSDEC
|
||||
| "*." ->
|
||||
update_acc lexbuf;
|
||||
MULTDEC
|
||||
| "/." ->
|
||||
update_acc lexbuf;
|
||||
DIVDEC
|
||||
| "<=" ->
|
||||
update_acc lexbuf;
|
||||
LESSER_EQUAL
|
||||
@ -308,18 +438,6 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| '>' ->
|
||||
update_acc lexbuf;
|
||||
GREATER
|
||||
| "!=" ->
|
||||
update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
update_acc lexbuf;
|
||||
EQUAL
|
||||
| '(' ->
|
||||
update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
update_acc lexbuf;
|
||||
RPAREN
|
||||
| '+' ->
|
||||
update_acc lexbuf;
|
||||
PLUS
|
||||
@ -329,21 +447,51 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| '*' ->
|
||||
update_acc lexbuf;
|
||||
MULT
|
||||
| '%' ->
|
||||
update_acc lexbuf;
|
||||
PERCENT
|
||||
| '/' ->
|
||||
update_acc lexbuf;
|
||||
DIV
|
||||
| "!=" ->
|
||||
update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
update_acc lexbuf;
|
||||
EQUAL
|
||||
| '%' ->
|
||||
update_acc lexbuf;
|
||||
PERCENT
|
||||
| '(' ->
|
||||
update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
update_acc lexbuf;
|
||||
RPAREN
|
||||
| '{' ->
|
||||
update_acc lexbuf;
|
||||
LBRACKET
|
||||
| '}' ->
|
||||
update_acc lexbuf;
|
||||
RBRACKET
|
||||
| '[' ->
|
||||
update_acc lexbuf;
|
||||
LSQUARE
|
||||
| ']' ->
|
||||
update_acc lexbuf;
|
||||
RSQUARE
|
||||
| '|' ->
|
||||
update_acc lexbuf;
|
||||
VERTICAL
|
||||
| ':' ->
|
||||
update_acc lexbuf;
|
||||
COLON
|
||||
| ';' ->
|
||||
update_acc lexbuf;
|
||||
SEMICOLON
|
||||
| "--" ->
|
||||
update_acc lexbuf;
|
||||
ALT
|
||||
| '.' ->
|
||||
update_acc lexbuf;
|
||||
DOT
|
||||
| uppercase, Star (uppercase | lowercase | '0' .. '9' | '_' | '\'') ->
|
||||
(* Name of constructor *)
|
||||
update_acc lexbuf;
|
||||
@ -355,12 +503,14 @@ let rec lex_code (lexbuf : lexbuf) : token =
|
||||
| Plus '0' .. '9' ->
|
||||
(* Integer literal*)
|
||||
update_acc lexbuf;
|
||||
INT_LITERAL (int_of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
INT_LITERAL (Z.of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
let rec lex_law (lexbuf : lexbuf) : token =
|
||||
(** Main lexing function used outside code blocks *)
|
||||
let lex_law (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| '\n' -> lex_law lexbuf
|
||||
| "/*" ->
|
||||
is_code := true;
|
||||
code_string_acc := "";
|
||||
@ -384,8 +534,8 @@ let rec lex_law (lexbuf : lexbuf) : token =
|
||||
let name = get_component 1 in
|
||||
let pages = try Some (int_of_string (get_component 3)) with Not_found -> None in
|
||||
let pos = lexing_positions lexbuf in
|
||||
if Filename.extension name = ".pdf" then LAW_INCLUDE (Catala_ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Catala_ast.CatalaFile (name, pos))
|
||||
if Filename.extension name = ".pdf" then LAW_INCLUDE (Ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Ast.CatalaFile (name, pos))
|
||||
| "@@", Plus (Compl '@'), "@@", Star '+' ->
|
||||
let extract_code_title = R.regexp "@@([^@]+)@@([\\+]*)" in
|
||||
let get_match = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
@ -413,7 +563,9 @@ let rec lex_law (lexbuf : lexbuf) : token =
|
||||
done;
|
||||
|
||||
LAW_ARTICLE (title, None, None)
|
||||
| Plus (Compl ('@' | '/' | '\n')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
| Plus (Compl ('@' | '/')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
(** Entry point of the lexer, distributes to {!val: lex_code} or {!val: lex_law} depending of {!val:
|
||||
is_code}. *)
|
||||
let lexer lexbuf = if !is_code then lex_code lexbuf else lex_law lexbuf
|
||||
|
@ -14,9 +14,13 @@
|
||||
|
||||
open Parser
|
||||
open Sedlexing
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module L = Lexer
|
||||
module R = Re.Pcre
|
||||
|
||||
(** Same as {!val: Surface.Lexer.token_list_language_agnostic}, but with tokens specialized to
|
||||
English. *)
|
||||
let token_list_en : (string * token) list =
|
||||
[
|
||||
("scope", SCOPE);
|
||||
@ -31,14 +35,17 @@ let token_list_en : (string * token) list =
|
||||
("collection", COLLECTION);
|
||||
("enumeration", ENUM);
|
||||
("integer", INTEGER);
|
||||
("amount", MONEY);
|
||||
("money", MONEY);
|
||||
("text", TEXT);
|
||||
("decimal", DECIMAL);
|
||||
("date", DATE);
|
||||
("duration", DURATION);
|
||||
("boolean", BOOLEAN);
|
||||
("sum", SUM);
|
||||
("fulfilled", FILLED);
|
||||
("definition", DEFINITION);
|
||||
("label", LABEL);
|
||||
("exception", EXCEPTION);
|
||||
("equals", DEFINED_AS);
|
||||
("match", MATCH);
|
||||
("with pattern", WITH);
|
||||
@ -48,7 +55,6 @@ let token_list_en : (string * token) list =
|
||||
("else", ELSE);
|
||||
("content", CONTENT);
|
||||
("structure", STRUCT);
|
||||
("optional", OPTIONAL);
|
||||
("assertion", ASSERTION);
|
||||
("varies", VARIES);
|
||||
("with", WITH_V);
|
||||
@ -61,18 +67,25 @@ let token_list_en : (string * token) list =
|
||||
("exists", EXISTS);
|
||||
("such", SUCH);
|
||||
("that", THAT);
|
||||
("now", NOW);
|
||||
("and", AND);
|
||||
("or", OR);
|
||||
("not", NOT);
|
||||
("integer_to_decimal", INT_TO_DEC);
|
||||
("maximum", MAXIMUM);
|
||||
("minimum", MAXIMUM);
|
||||
("number", CARDINAL);
|
||||
("year", YEAR);
|
||||
("month", MONTH);
|
||||
("day", DAY);
|
||||
("true", TRUE);
|
||||
("false", FALSE);
|
||||
]
|
||||
@ L.token_list_language_agnostic
|
||||
|
||||
(** Main lexing function used in code blocks *)
|
||||
let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| white_space ->
|
||||
(* Whitespaces *)
|
||||
@ -119,7 +132,7 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "integer" ->
|
||||
L.update_acc lexbuf;
|
||||
INTEGER
|
||||
| "amount" ->
|
||||
| "money" ->
|
||||
L.update_acc lexbuf;
|
||||
MONEY
|
||||
| "text" ->
|
||||
@ -131,6 +144,9 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "date" ->
|
||||
L.update_acc lexbuf;
|
||||
DATE
|
||||
| "duration" ->
|
||||
L.update_acc lexbuf;
|
||||
DURATION
|
||||
| "boolean" ->
|
||||
L.update_acc lexbuf;
|
||||
BOOLEAN
|
||||
@ -143,6 +159,12 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "definition" ->
|
||||
L.update_acc lexbuf;
|
||||
DEFINITION
|
||||
| "label" ->
|
||||
L.update_acc lexbuf;
|
||||
LABEL
|
||||
| "exception" ->
|
||||
L.update_acc lexbuf;
|
||||
EXCEPTION
|
||||
| "equals" ->
|
||||
L.update_acc lexbuf;
|
||||
DEFINED_AS
|
||||
@ -176,9 +198,6 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "structure" ->
|
||||
L.update_acc lexbuf;
|
||||
STRUCT
|
||||
| "optional" ->
|
||||
L.update_acc lexbuf;
|
||||
OPTIONAL
|
||||
| "assertion" ->
|
||||
L.update_acc lexbuf;
|
||||
ASSERTION
|
||||
@ -219,9 +238,6 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "that" ->
|
||||
L.update_acc lexbuf;
|
||||
THAT
|
||||
| "now" ->
|
||||
L.update_acc lexbuf;
|
||||
NOW
|
||||
| "and" ->
|
||||
L.update_acc lexbuf;
|
||||
AND
|
||||
@ -231,6 +247,15 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "not" ->
|
||||
L.update_acc lexbuf;
|
||||
NOT
|
||||
| "integer_to_decimal" ->
|
||||
L.update_acc lexbuf;
|
||||
INT_TO_DEC
|
||||
| "maximum" ->
|
||||
L.update_acc lexbuf;
|
||||
MAXIMUM
|
||||
| "minimum" ->
|
||||
L.update_acc lexbuf;
|
||||
MINIMUM
|
||||
| "number" ->
|
||||
L.update_acc lexbuf;
|
||||
CARDINAL
|
||||
@ -243,6 +268,12 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| "year" ->
|
||||
L.update_acc lexbuf;
|
||||
YEAR
|
||||
| "month" ->
|
||||
L.update_acc lexbuf;
|
||||
MONTH
|
||||
| "day" ->
|
||||
L.update_acc lexbuf;
|
||||
DAY
|
||||
| 0x24, Star white_space, '0' .. '9', Star ('0' .. '9' | ','), Opt ('.', Rep ('0' .. '9', 0 .. 2))
|
||||
->
|
||||
let extract_parts = R.regexp "([0-9]([0-9,]*[0-9]|))(.([0-9]{0,2})|)" in
|
||||
@ -252,8 +283,8 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
(* Integer literal*)
|
||||
let units = parts 1 in
|
||||
let remove_commas = R.regexp "," in
|
||||
let units = int_of_string (R.substitute ~rex:remove_commas ~subst:(fun _ -> "") units) in
|
||||
let cents = try int_of_string (parts 4) with Not_found -> 0 in
|
||||
let units = Z.of_string (R.substitute ~rex:remove_commas ~subst:(fun _ -> "") units) in
|
||||
let cents = try Z.of_string (parts 4) with Not_found -> Z.zero in
|
||||
L.update_acc lexbuf;
|
||||
MONEY_AMOUNT (units, cents)
|
||||
| Plus '0' .. '9', '.', Star '0' .. '9' ->
|
||||
@ -261,13 +292,94 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
let dec_parts = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
(* Integer literal*)
|
||||
L.update_acc lexbuf;
|
||||
DECIMAL_LITERAL (int_of_string (dec_parts 1), int_of_string (dec_parts 2))
|
||||
DECIMAL_LITERAL (Z.of_string (dec_parts 1), Z.of_string (dec_parts 2))
|
||||
| "->" ->
|
||||
L.update_acc lexbuf;
|
||||
ARROW
|
||||
| '.' ->
|
||||
| "<=@" ->
|
||||
L.update_acc lexbuf;
|
||||
DOT
|
||||
LESSER_EQUAL_DATE
|
||||
| "<@" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DATE
|
||||
| ">=@" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DATE
|
||||
| ">@" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DATE
|
||||
| "-@" ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDATE
|
||||
| "+@" ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDATE
|
||||
| "<=^" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_DURATION
|
||||
| "<^" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DURATION
|
||||
| ">=^" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DURATION
|
||||
| ">^" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DURATION
|
||||
| "+^" ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDURATION
|
||||
| "-^" ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDURATION
|
||||
| "<=", 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_MONEY
|
||||
| '<', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_MONEY
|
||||
| ">=", 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_MONEY
|
||||
| '>', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_MONEY
|
||||
| '+', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSMONEY
|
||||
| '-', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSMONEY
|
||||
| '*', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
MULTMONEY
|
||||
| '/', 0x24 ->
|
||||
L.update_acc lexbuf;
|
||||
DIVMONEY
|
||||
| "<=." ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_DEC
|
||||
| "<." ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DEC
|
||||
| ">=." ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DEC
|
||||
| ">." ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DEC
|
||||
| "+." ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDEC
|
||||
| "-." ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDEC
|
||||
| "*." ->
|
||||
L.update_acc lexbuf;
|
||||
MULTDEC
|
||||
| "/." ->
|
||||
L.update_acc lexbuf;
|
||||
DIVDEC
|
||||
| "<=" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL
|
||||
@ -280,18 +392,6 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| '>' ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER
|
||||
| "!=" ->
|
||||
L.update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
L.update_acc lexbuf;
|
||||
EQUAL
|
||||
| '(' ->
|
||||
L.update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
L.update_acc lexbuf;
|
||||
RPAREN
|
||||
| '+' ->
|
||||
L.update_acc lexbuf;
|
||||
PLUS
|
||||
@ -301,21 +401,51 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| '*' ->
|
||||
L.update_acc lexbuf;
|
||||
MULT
|
||||
| '%' ->
|
||||
L.update_acc lexbuf;
|
||||
PERCENT
|
||||
| '/' ->
|
||||
L.update_acc lexbuf;
|
||||
DIV
|
||||
| "!=" ->
|
||||
L.update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
L.update_acc lexbuf;
|
||||
EQUAL
|
||||
| '%' ->
|
||||
L.update_acc lexbuf;
|
||||
PERCENT
|
||||
| '(' ->
|
||||
L.update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
L.update_acc lexbuf;
|
||||
RPAREN
|
||||
| '{' ->
|
||||
L.update_acc lexbuf;
|
||||
LBRACKET
|
||||
| '}' ->
|
||||
L.update_acc lexbuf;
|
||||
RBRACKET
|
||||
| '[' ->
|
||||
L.update_acc lexbuf;
|
||||
LSQUARE
|
||||
| ']' ->
|
||||
L.update_acc lexbuf;
|
||||
RSQUARE
|
||||
| '|' ->
|
||||
L.update_acc lexbuf;
|
||||
VERTICAL
|
||||
| ':' ->
|
||||
L.update_acc lexbuf;
|
||||
COLON
|
||||
| ';' ->
|
||||
L.update_acc lexbuf;
|
||||
SEMICOLON
|
||||
| "--" ->
|
||||
L.update_acc lexbuf;
|
||||
ALT
|
||||
| '.' ->
|
||||
L.update_acc lexbuf;
|
||||
DOT
|
||||
| uppercase, Star (uppercase | lowercase | '0' .. '9' | '_' | '\'') ->
|
||||
(* Name of constructor *)
|
||||
L.update_acc lexbuf;
|
||||
@ -327,12 +457,14 @@ let rec lex_code_en (lexbuf : lexbuf) : token =
|
||||
| Plus '0' .. '9' ->
|
||||
(* Integer literal*)
|
||||
L.update_acc lexbuf;
|
||||
INT_LITERAL (int_of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> L.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
INT_LITERAL (Z.of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> L.raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
let rec lex_law_en (lexbuf : lexbuf) : token =
|
||||
(** Main lexing function used outside code blocks *)
|
||||
let lex_law_en (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| '\n' -> lex_law_en lexbuf
|
||||
| "/*" ->
|
||||
L.is_code := true;
|
||||
L.code_string_acc := "";
|
||||
@ -356,8 +488,8 @@ let rec lex_law_en (lexbuf : lexbuf) : token =
|
||||
let name = get_component 1 in
|
||||
let pages = try Some (int_of_string (get_component 3)) with Not_found -> None in
|
||||
let pos = lexing_positions lexbuf in
|
||||
if Filename.extension name = ".pdf" then LAW_INCLUDE (Catala_ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Catala_ast.CatalaFile (name, pos))
|
||||
if Filename.extension name = ".pdf" then LAW_INCLUDE (Ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Ast.CatalaFile (name, pos))
|
||||
| "@@", Plus (Compl '@'), "@@", Star '+' ->
|
||||
let extract_code_title = R.regexp "@@([^@]+)@@([\\+]*)" in
|
||||
let get_match = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
@ -385,7 +517,7 @@ let rec lex_law_en (lexbuf : lexbuf) : token =
|
||||
done;
|
||||
|
||||
LAW_ARTICLE (title, None, None)
|
||||
| Plus (Compl ('@' | '/' | '\n')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> L.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
| Plus (Compl ('@' | '/')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> L.raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
let lexer_en lexbuf = if !L.is_code then lex_code_en lexbuf else lex_law_en lexbuf
|
||||
|
@ -14,9 +14,13 @@
|
||||
|
||||
open Parser
|
||||
open Sedlexing
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module L = Lexer
|
||||
module R = Re.Pcre
|
||||
|
||||
(** Same as {!val: Surface.Lexer.token_list_language_agnostic}, but with tokens specialized to
|
||||
French. *)
|
||||
let token_list_fr : (string * token) list =
|
||||
[
|
||||
("champ d'application", SCOPE);
|
||||
@ -31,10 +35,11 @@ let token_list_fr : (string * token) list =
|
||||
("collection", COLLECTION);
|
||||
("énumération", ENUM);
|
||||
("entier", INTEGER);
|
||||
("montant", MONEY);
|
||||
("argent", MONEY);
|
||||
("texte", TEXT);
|
||||
("decimal", DECIMAL);
|
||||
("date", DATE);
|
||||
("durée", DURATION);
|
||||
("booléen", BOOLEAN);
|
||||
("somme", SUM);
|
||||
("rempli", FILLED);
|
||||
@ -48,7 +53,6 @@ let token_list_fr : (string * token) list =
|
||||
("sinon", ELSE);
|
||||
("contenu", CONTENT);
|
||||
("structure", STRUCT);
|
||||
("optionnel", OPTIONAL);
|
||||
("assertion", ASSERTION);
|
||||
("varie", VARIES);
|
||||
("avec", WITH_V);
|
||||
@ -61,18 +65,25 @@ let token_list_fr : (string * token) list =
|
||||
("existe", EXISTS);
|
||||
("tel", SUCH);
|
||||
("que", THAT);
|
||||
("maintenant", NOW);
|
||||
("et", AND);
|
||||
("ou", OR);
|
||||
("non", NOT);
|
||||
("nombre", CARDINAL);
|
||||
("entier_vers_décimal", INT_TO_DEC);
|
||||
("maximum", MAXIMUM);
|
||||
("minimum", MINIMUM);
|
||||
("an", YEAR);
|
||||
("mois", MONTH);
|
||||
("jour", DAY);
|
||||
("vrai", TRUE);
|
||||
("faux", FALSE);
|
||||
]
|
||||
@ L.token_list_language_agnostic
|
||||
|
||||
(** Main lexing function used in code blocks *)
|
||||
let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| white_space | '\n' ->
|
||||
(* Whitespaces *)
|
||||
@ -120,7 +131,7 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "entier" ->
|
||||
L.update_acc lexbuf;
|
||||
INTEGER
|
||||
| "montant" ->
|
||||
| "argent" ->
|
||||
L.update_acc lexbuf;
|
||||
MONEY
|
||||
| "texte" ->
|
||||
@ -132,6 +143,9 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "date" ->
|
||||
L.update_acc lexbuf;
|
||||
DATE
|
||||
| "dur", 0xE9, "e" ->
|
||||
L.update_acc lexbuf;
|
||||
DURATION
|
||||
| "bool", 0xE9, "en" ->
|
||||
L.update_acc lexbuf;
|
||||
BOOLEAN
|
||||
@ -145,6 +159,12 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
(* 0xE9 is é *)
|
||||
L.update_acc lexbuf;
|
||||
DEFINITION
|
||||
| 0xE9, "tiquette" ->
|
||||
L.update_acc lexbuf;
|
||||
LABEL
|
||||
| "exception" ->
|
||||
L.update_acc lexbuf;
|
||||
EXCEPTION
|
||||
| 0xE9, "gal ", 0x00E0 ->
|
||||
(* 0xE9 is é *)
|
||||
L.update_acc lexbuf;
|
||||
@ -181,9 +201,6 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "structure" ->
|
||||
L.update_acc lexbuf;
|
||||
STRUCT
|
||||
| "optionnel" ->
|
||||
L.update_acc lexbuf;
|
||||
OPTIONAL
|
||||
| "assertion" ->
|
||||
L.update_acc lexbuf;
|
||||
ASSERTION
|
||||
@ -225,9 +242,6 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "que" ->
|
||||
L.update_acc lexbuf;
|
||||
THAT
|
||||
| "maintenant" ->
|
||||
L.update_acc lexbuf;
|
||||
NOW
|
||||
| "et" ->
|
||||
L.update_acc lexbuf;
|
||||
AND
|
||||
@ -240,6 +254,15 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "nombre" ->
|
||||
L.update_acc lexbuf;
|
||||
CARDINAL
|
||||
| "maximum" ->
|
||||
L.update_acc lexbuf;
|
||||
MAXIMUM
|
||||
| "minimum" ->
|
||||
L.update_acc lexbuf;
|
||||
MINIMUM
|
||||
| "entier_vers_d", 0xE9, "cimal" ->
|
||||
L.update_acc lexbuf;
|
||||
INT_TO_DEC
|
||||
| "vrai" ->
|
||||
L.update_acc lexbuf;
|
||||
TRUE
|
||||
@ -249,6 +272,12 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| "an" ->
|
||||
L.update_acc lexbuf;
|
||||
YEAR
|
||||
| "mois" ->
|
||||
L.update_acc lexbuf;
|
||||
MONTH
|
||||
| "jour" ->
|
||||
L.update_acc lexbuf;
|
||||
DAY
|
||||
| ( '0' .. '9',
|
||||
Star ('0' .. '9' | white_space),
|
||||
Opt (',', Rep ('0' .. '9', 0 .. 2)),
|
||||
@ -261,8 +290,8 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
(* Integer literal*)
|
||||
let units = parts 1 in
|
||||
let remove_spaces = R.regexp " " in
|
||||
let units = int_of_string (R.substitute ~rex:remove_spaces ~subst:(fun _ -> "") units) in
|
||||
let cents = try int_of_string (parts 4) with Not_found -> 0 in
|
||||
let units = Z.of_string (R.substitute ~rex:remove_spaces ~subst:(fun _ -> "") units) in
|
||||
let cents = try Z.of_string (parts 4) with Not_found -> Z.zero in
|
||||
L.update_acc lexbuf;
|
||||
MONEY_AMOUNT (units, cents)
|
||||
| Plus '0' .. '9', ',', Star '0' .. '9' ->
|
||||
@ -270,13 +299,94 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
let dec_parts = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
(* Integer literal*)
|
||||
L.update_acc lexbuf;
|
||||
DECIMAL_LITERAL (int_of_string (dec_parts 1), int_of_string (dec_parts 2))
|
||||
DECIMAL_LITERAL (Z.of_string (dec_parts 1), Z.of_string (dec_parts 2))
|
||||
| "->" ->
|
||||
L.update_acc lexbuf;
|
||||
ARROW
|
||||
| '.' ->
|
||||
| "<=@" ->
|
||||
L.update_acc lexbuf;
|
||||
DOT
|
||||
LESSER_EQUAL_DATE
|
||||
| "<@" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DATE
|
||||
| ">=@" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DATE
|
||||
| ">@" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DATE
|
||||
| "-@" ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDATE
|
||||
| "+@" ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDATE
|
||||
| "<=^" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_DURATION
|
||||
| "<^" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DURATION
|
||||
| ">=^" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DURATION
|
||||
| ">^" ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DURATION
|
||||
| "+^" ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDURATION
|
||||
| "-^" ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDURATION
|
||||
| "<=", 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_MONEY
|
||||
| '<', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_MONEY
|
||||
| ">=", 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_MONEY
|
||||
| '>', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_MONEY
|
||||
| '+', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSMONEY
|
||||
| '-', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSMONEY
|
||||
| '*', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
MULTMONEY
|
||||
| '/', 0x20AC ->
|
||||
L.update_acc lexbuf;
|
||||
DIVMONEY
|
||||
| "<=." ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL_DEC
|
||||
| "<." ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_DEC
|
||||
| ">=." ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_EQUAL_DEC
|
||||
| ">." ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER_DEC
|
||||
| "+." ->
|
||||
L.update_acc lexbuf;
|
||||
PLUSDEC
|
||||
| "-." ->
|
||||
L.update_acc lexbuf;
|
||||
MINUSDEC
|
||||
| "*." ->
|
||||
L.update_acc lexbuf;
|
||||
MULTDEC
|
||||
| "/." ->
|
||||
L.update_acc lexbuf;
|
||||
DIVDEC
|
||||
| "<=" ->
|
||||
L.update_acc lexbuf;
|
||||
LESSER_EQUAL
|
||||
@ -289,18 +399,6 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| '>' ->
|
||||
L.update_acc lexbuf;
|
||||
GREATER
|
||||
| "!=" ->
|
||||
L.update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
L.update_acc lexbuf;
|
||||
EQUAL
|
||||
| '(' ->
|
||||
L.update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
L.update_acc lexbuf;
|
||||
RPAREN
|
||||
| '+' ->
|
||||
L.update_acc lexbuf;
|
||||
PLUS
|
||||
@ -310,21 +408,51 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| '*' ->
|
||||
L.update_acc lexbuf;
|
||||
MULT
|
||||
| '%' ->
|
||||
L.update_acc lexbuf;
|
||||
PERCENT
|
||||
| '/' ->
|
||||
L.update_acc lexbuf;
|
||||
DIV
|
||||
| "!=" ->
|
||||
L.update_acc lexbuf;
|
||||
NOT_EQUAL
|
||||
| '=' ->
|
||||
L.update_acc lexbuf;
|
||||
EQUAL
|
||||
| '%' ->
|
||||
L.update_acc lexbuf;
|
||||
PERCENT
|
||||
| '(' ->
|
||||
L.update_acc lexbuf;
|
||||
LPAREN
|
||||
| ')' ->
|
||||
L.update_acc lexbuf;
|
||||
RPAREN
|
||||
| '{' ->
|
||||
L.update_acc lexbuf;
|
||||
LBRACKET
|
||||
| '}' ->
|
||||
L.update_acc lexbuf;
|
||||
RBRACKET
|
||||
| '[' ->
|
||||
L.update_acc lexbuf;
|
||||
LSQUARE
|
||||
| ']' ->
|
||||
L.update_acc lexbuf;
|
||||
RSQUARE
|
||||
| '|' ->
|
||||
L.update_acc lexbuf;
|
||||
VERTICAL
|
||||
| ':' ->
|
||||
L.update_acc lexbuf;
|
||||
COLON
|
||||
| ';' ->
|
||||
L.update_acc lexbuf;
|
||||
SEMICOLON
|
||||
| "--" ->
|
||||
L.update_acc lexbuf;
|
||||
ALT
|
||||
| '.' ->
|
||||
L.update_acc lexbuf;
|
||||
DOT
|
||||
| uppercase, Star (uppercase | lowercase | '0' .. '9' | '_' | '\'') ->
|
||||
(* Name of constructor *)
|
||||
L.update_acc lexbuf;
|
||||
@ -336,12 +464,14 @@ let rec lex_code_fr (lexbuf : lexbuf) : token =
|
||||
| Plus '0' .. '9' ->
|
||||
(* Integer literal*)
|
||||
L.update_acc lexbuf;
|
||||
INT_LITERAL (int_of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> L.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
INT_LITERAL (Z.of_string (Utf8.lexeme lexbuf))
|
||||
| _ -> L.raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
let rec lex_law_fr (lexbuf : lexbuf) : token =
|
||||
(** Main lexing function used outside code blocks *)
|
||||
let lex_law_fr (lexbuf : lexbuf) : token =
|
||||
let prev_lexeme = Utf8.lexeme lexbuf in
|
||||
let prev_pos = lexing_positions lexbuf in
|
||||
match%sedlex lexbuf with
|
||||
| '\n' -> lex_law_fr lexbuf
|
||||
| "/*" ->
|
||||
L.is_code := true;
|
||||
L.code_string_acc := "";
|
||||
@ -371,10 +501,9 @@ let rec lex_law_fr (lexbuf : lexbuf) : token =
|
||||
let name = get_component 1 in
|
||||
let pages = try Some (int_of_string (get_component 3)) with Not_found -> None in
|
||||
let pos = lexing_positions lexbuf in
|
||||
if R.pmatch ~rex:jorftext name then LAW_INCLUDE (Catala_ast.LegislativeText (name, pos))
|
||||
else if Filename.extension name = ".pdf" then
|
||||
LAW_INCLUDE (Catala_ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Catala_ast.CatalaFile (name, pos))
|
||||
if R.pmatch ~rex:jorftext name then LAW_INCLUDE (Ast.LegislativeText (name, pos))
|
||||
else if Filename.extension name = ".pdf" then LAW_INCLUDE (Ast.PdfFile ((name, pos), pages))
|
||||
else LAW_INCLUDE (Ast.CatalaFile (name, pos))
|
||||
| "@@", Plus (Compl '@'), "@@", Star '+' ->
|
||||
let extract_code_title = R.regexp "@@([^@]+)@@([\\+]*)" in
|
||||
let get_match = R.get_substring (R.exec ~rex:extract_code_title (Utf8.lexeme lexbuf)) in
|
||||
@ -410,8 +539,10 @@ let rec lex_law_fr (lexbuf : lexbuf) : token =
|
||||
done;
|
||||
|
||||
LAW_ARTICLE (title, article_id, article_expiration_date)
|
||||
| Plus (Compl ('@' | '/' | '\n')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> L.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "unknown token"
|
||||
| Plus (Compl ('@' | '/')) -> LAW_TEXT (Utf8.lexeme lexbuf)
|
||||
| _ -> L.raise_lexer_error prev_pos prev_lexeme
|
||||
|
||||
(** Entry point of the lexer, distributes to {!val: lex_code_fr} or {!val: lex_law_fr} depending of
|
||||
{!val: Surface.Lexer.is_code}. *)
|
||||
let lexer_fr (lexbuf : lexbuf) : token =
|
||||
if !L.is_code then lex_code_fr lexbuf else lex_law_fr lexbuf
|
||||
|
@ -15,152 +15,263 @@
|
||||
(** Builds a context that allows for mapping each name to a precise uid, taking lexical scopes into
|
||||
account *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
|
||||
(** {1 Name resolution context} *)
|
||||
|
||||
type ident = string
|
||||
|
||||
type typ = Lambda_ast.typ
|
||||
|
||||
type def_context = { var_idmap : Uid.LocalVar.t Uid.IdentMap.t }
|
||||
(** Inside a definition, local variables can be introduced by functions arguments or pattern
|
||||
matching *)
|
||||
type typ = Scopelang.Ast.typ
|
||||
|
||||
type scope_context = {
|
||||
var_idmap : Uid.Var.t Uid.IdentMap.t;
|
||||
sub_scopes_idmap : Uid.SubScope.t Uid.IdentMap.t;
|
||||
sub_scopes : Uid.Scope.t Uid.SubScopeMap.t;
|
||||
definitions : def_context Uid.ScopeDefMap.t;
|
||||
(** Contains the local variables in all the definitions *)
|
||||
var_idmap : Scopelang.Ast.ScopeVar.t Desugared.Ast.IdentMap.t; (** Scope variables *)
|
||||
label_idmap : Desugared.Ast.RuleName.t Desugared.Ast.IdentMap.t;
|
||||
sub_scopes_idmap : Scopelang.Ast.SubScopeName.t Desugared.Ast.IdentMap.t;
|
||||
(** Sub-scopes variables *)
|
||||
sub_scopes : Scopelang.Ast.ScopeName.t Scopelang.Ast.SubScopeMap.t;
|
||||
(** To what scope sub-scopes refer to? *)
|
||||
}
|
||||
(** Inside a scope, we distinguish between the variables and the subscopes. *)
|
||||
|
||||
type struct_context = typ Pos.marked Scopelang.Ast.StructFieldMap.t
|
||||
(** Types of the fields of a struct *)
|
||||
|
||||
type enum_context = typ Pos.marked Scopelang.Ast.EnumConstructorMap.t
|
||||
(** Types of the payloads of the cases of an enum *)
|
||||
|
||||
type context = {
|
||||
scope_idmap : Uid.Scope.t Uid.IdentMap.t;
|
||||
scopes : scope_context Uid.ScopeMap.t;
|
||||
var_typs : typ Uid.VarMap.t;
|
||||
local_var_idmap : Scopelang.Ast.Var.t Desugared.Ast.IdentMap.t;
|
||||
(** Inside a definition, local variables can be introduced by functions arguments or pattern
|
||||
matching *)
|
||||
scope_idmap : Scopelang.Ast.ScopeName.t Desugared.Ast.IdentMap.t; (** The names of the scopes *)
|
||||
struct_idmap : Scopelang.Ast.StructName.t Desugared.Ast.IdentMap.t;
|
||||
(** The names of the structs *)
|
||||
field_idmap : Scopelang.Ast.StructFieldName.t Scopelang.Ast.StructMap.t Desugared.Ast.IdentMap.t;
|
||||
(** The names of the struct fields. Names of fields can be shared between different structs *)
|
||||
enum_idmap : Scopelang.Ast.EnumName.t Desugared.Ast.IdentMap.t; (** The names of the enums *)
|
||||
constructor_idmap :
|
||||
Scopelang.Ast.EnumConstructor.t Scopelang.Ast.EnumMap.t Desugared.Ast.IdentMap.t;
|
||||
(** The names of the enum constructors. Constructor names can be shared between different
|
||||
enums *)
|
||||
scopes : scope_context Scopelang.Ast.ScopeMap.t; (** For each scope, its context *)
|
||||
structs : struct_context Scopelang.Ast.StructMap.t; (** For each struct, its context *)
|
||||
enums : enum_context Scopelang.Ast.EnumMap.t; (** For each enum, its context *)
|
||||
var_typs : (typ Pos.marked * bool) (* is it a condition? *) Scopelang.Ast.ScopeVarMap.t;
|
||||
(** The types of each scope variable declared *)
|
||||
}
|
||||
(** Main context used throughout {!module: Surface.Desugaring} *)
|
||||
|
||||
(** {1 Helpers} *)
|
||||
|
||||
(** Temporary function raising an error message saying that a feature is not supported yet *)
|
||||
let raise_unsupported_feature (msg : string) (pos : Pos.t) =
|
||||
Errors.raise_spanned_error (Printf.sprintf "unsupported feature: %s" msg) pos
|
||||
Errors.raise_spanned_error (Printf.sprintf "Unsupported feature: %s" msg) pos
|
||||
|
||||
(** Function to call whenever an identifier used somewhere has not been declared in the program
|
||||
previously *)
|
||||
let raise_unknown_identifier (msg : string) (ident : ident Pos.marked) =
|
||||
Errors.raise_spanned_error
|
||||
(Printf.sprintf "%s: unknown identifier %s" (Pos.unmark ident) msg)
|
||||
(Printf.sprintf "\"%s\": unknown identifier %s" (Pos.unmark ident) msg)
|
||||
(Pos.get_position ident)
|
||||
|
||||
(** Get the type associated to an uid *)
|
||||
let get_var_typ (ctxt : context) (uid : Uid.Var.t) : typ = Uid.VarMap.find uid ctxt.var_typs
|
||||
(** Gets the type associated to an uid *)
|
||||
let get_var_typ (ctxt : context) (uid : Scopelang.Ast.ScopeVar.t) : typ Pos.marked =
|
||||
fst (Scopelang.Ast.ScopeVarMap.find uid ctxt.var_typs)
|
||||
|
||||
let is_var_cond (ctxt : context) (uid : Scopelang.Ast.ScopeVar.t) : bool =
|
||||
snd (Scopelang.Ast.ScopeVarMap.find uid ctxt.var_typs)
|
||||
|
||||
(** Get the variable uid inside the scope given in argument *)
|
||||
let get_var_uid (scope_uid : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
((x, pos) : ident Pos.marked) : Scopelang.Ast.ScopeVar.t =
|
||||
let scope = Scopelang.Ast.ScopeMap.find scope_uid ctxt.scopes in
|
||||
match Desugared.Ast.IdentMap.find_opt x scope.var_idmap with
|
||||
| None -> raise_unknown_identifier "for a var of this scope" (x, pos)
|
||||
| Some uid -> uid
|
||||
|
||||
(** Get the subscope uid inside the scope given in argument *)
|
||||
let get_subscope_uid (scope_uid : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
((y, pos) : ident Pos.marked) : Scopelang.Ast.SubScopeName.t =
|
||||
let scope = Scopelang.Ast.ScopeMap.find scope_uid ctxt.scopes in
|
||||
match Desugared.Ast.IdentMap.find_opt y scope.sub_scopes_idmap with
|
||||
| None -> raise_unknown_identifier "for a subscope of this scope" (y, pos)
|
||||
| Some sub_uid -> sub_uid
|
||||
|
||||
(** [is_subscope_uid scope_uid ctxt y] returns true if [y] belongs to the subscopes of [scope_uid]. *)
|
||||
let is_subscope_uid (scope_uid : Scopelang.Ast.ScopeName.t) (ctxt : context) (y : ident) : bool =
|
||||
let scope = Scopelang.Ast.ScopeMap.find scope_uid ctxt.scopes in
|
||||
Desugared.Ast.IdentMap.mem y scope.sub_scopes_idmap
|
||||
|
||||
(** Checks if the var_uid belongs to the scope scope_uid *)
|
||||
let belongs_to (ctxt : context) (uid : Scopelang.Ast.ScopeVar.t)
|
||||
(scope_uid : Scopelang.Ast.ScopeName.t) : bool =
|
||||
let scope = Scopelang.Ast.ScopeMap.find scope_uid ctxt.scopes in
|
||||
Desugared.Ast.IdentMap.exists
|
||||
(fun _ var_uid -> Scopelang.Ast.ScopeVar.compare uid var_uid = 0)
|
||||
scope.var_idmap
|
||||
|
||||
(** Retrieves the type of a scope definition from the context *)
|
||||
let get_def_typ (ctxt : context) (def : Desugared.Ast.ScopeDef.t) : typ Pos.marked =
|
||||
match def with
|
||||
| Desugared.Ast.ScopeDef.SubScopeVar (_, x)
|
||||
(* we don't need to look at the subscope prefix because [x] is already the uid referring back to
|
||||
the original subscope *)
|
||||
| Desugared.Ast.ScopeDef.Var x ->
|
||||
get_var_typ ctxt x
|
||||
|
||||
let is_def_cond (ctxt : context) (def : Desugared.Ast.ScopeDef.t) : bool =
|
||||
match def with
|
||||
| Desugared.Ast.ScopeDef.SubScopeVar (_, x)
|
||||
(* we don't need to look at the subscope prefix because [x] is already the uid referring back to
|
||||
the original subscope *)
|
||||
| Desugared.Ast.ScopeDef.Var x ->
|
||||
is_var_cond ctxt x
|
||||
|
||||
(** {1 Declarations pass} *)
|
||||
|
||||
(** Process a subscope declaration *)
|
||||
let process_subscope_decl (scope : Uid.Scope.t) (ctxt : context)
|
||||
(decl : Catala_ast.scope_decl_context_scope) : context =
|
||||
let process_subscope_decl (scope : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
(decl : Ast.scope_decl_context_scope) : context =
|
||||
let name, name_pos = decl.scope_decl_context_scope_name in
|
||||
let subscope, s_pos = decl.scope_decl_context_scope_sub_scope in
|
||||
let scope_ctxt = Uid.ScopeMap.find scope ctxt.scopes in
|
||||
match Uid.IdentMap.find_opt subscope scope_ctxt.sub_scopes_idmap with
|
||||
let scope_ctxt = Scopelang.Ast.ScopeMap.find scope ctxt.scopes in
|
||||
match Desugared.Ast.IdentMap.find_opt subscope scope_ctxt.sub_scopes_idmap with
|
||||
| Some use ->
|
||||
Errors.raise_multispanned_error "subscope name already used"
|
||||
Errors.raise_multispanned_error "Subscope name already used"
|
||||
[
|
||||
(Some "first use", Pos.get_position (Uid.SubScope.get_info use));
|
||||
(Some "first use", Pos.get_position (Scopelang.Ast.SubScopeName.get_info use));
|
||||
(Some "second use", s_pos);
|
||||
]
|
||||
| None ->
|
||||
let sub_scope_uid = Uid.SubScope.fresh (name, name_pos) in
|
||||
let sub_scope_uid = Scopelang.Ast.SubScopeName.fresh (name, name_pos) in
|
||||
let original_subscope_uid =
|
||||
match Uid.IdentMap.find_opt subscope ctxt.scope_idmap with
|
||||
match Desugared.Ast.IdentMap.find_opt subscope ctxt.scope_idmap with
|
||||
| None -> raise_unknown_identifier "for a scope" (subscope, s_pos)
|
||||
| Some id -> id
|
||||
in
|
||||
let scope_ctxt =
|
||||
{
|
||||
scope_ctxt with
|
||||
sub_scopes_idmap = Uid.IdentMap.add name sub_scope_uid scope_ctxt.sub_scopes_idmap;
|
||||
sub_scopes = Uid.SubScopeMap.add sub_scope_uid original_subscope_uid scope_ctxt.sub_scopes;
|
||||
sub_scopes_idmap =
|
||||
Desugared.Ast.IdentMap.add name sub_scope_uid scope_ctxt.sub_scopes_idmap;
|
||||
sub_scopes =
|
||||
Scopelang.Ast.SubScopeMap.add sub_scope_uid original_subscope_uid scope_ctxt.sub_scopes;
|
||||
}
|
||||
in
|
||||
{ ctxt with scopes = Uid.ScopeMap.add scope scope_ctxt ctxt.scopes }
|
||||
{ ctxt with scopes = Scopelang.Ast.ScopeMap.add scope scope_ctxt ctxt.scopes }
|
||||
|
||||
let process_base_typ ((typ, typ_pos) : Catala_ast.base_typ Pos.marked) : Lambda_ast.typ =
|
||||
let is_type_cond ((typ, _) : Ast.typ Pos.marked) =
|
||||
match typ with
|
||||
| Catala_ast.Condition -> Lambda_ast.TBool
|
||||
| Catala_ast.Data (Catala_ast.Collection _) -> raise_unsupported_feature "collection type" typ_pos
|
||||
| Catala_ast.Data (Catala_ast.Optional _) -> raise_unsupported_feature "option type" typ_pos
|
||||
| Catala_ast.Data (Catala_ast.Primitive prim) -> (
|
||||
| Ast.Base Ast.Condition | Ast.Func { arg_typ = _; return_typ = Ast.Condition, _ } -> true
|
||||
| _ -> false
|
||||
|
||||
(** Process a basic type (all types except function types) *)
|
||||
let rec process_base_typ (ctxt : context) ((typ, typ_pos) : Ast.base_typ Pos.marked) :
|
||||
Scopelang.Ast.typ Pos.marked =
|
||||
match typ with
|
||||
| Ast.Condition -> (Scopelang.Ast.TLit TBool, typ_pos)
|
||||
| Ast.Data (Ast.Collection t) ->
|
||||
( Scopelang.Ast.TArray
|
||||
(Pos.unmark (process_base_typ ctxt (Ast.Data (Pos.unmark t), Pos.get_position t))),
|
||||
typ_pos )
|
||||
| Ast.Data (Ast.Primitive prim) -> (
|
||||
match prim with
|
||||
| Catala_ast.Integer | Catala_ast.Decimal | Catala_ast.Money | Catala_ast.Date ->
|
||||
Lambda_ast.TInt
|
||||
| Catala_ast.Boolean -> Lambda_ast.TBool
|
||||
| Catala_ast.Text -> raise_unsupported_feature "text type" typ_pos
|
||||
| Catala_ast.Named _ -> raise_unsupported_feature "struct or enum types" typ_pos )
|
||||
| Ast.Integer -> (Scopelang.Ast.TLit TInt, typ_pos)
|
||||
| Ast.Decimal -> (Scopelang.Ast.TLit TRat, typ_pos)
|
||||
| Ast.Money -> (Scopelang.Ast.TLit TMoney, typ_pos)
|
||||
| Ast.Duration -> (Scopelang.Ast.TLit TDuration, typ_pos)
|
||||
| Ast.Date -> (Scopelang.Ast.TLit TDate, typ_pos)
|
||||
| Ast.Boolean -> (Scopelang.Ast.TLit TBool, typ_pos)
|
||||
| Ast.Text -> raise_unsupported_feature "text type" typ_pos
|
||||
| Ast.Named ident -> (
|
||||
match Desugared.Ast.IdentMap.find_opt ident ctxt.struct_idmap with
|
||||
| Some s_uid -> (Scopelang.Ast.TStruct s_uid, typ_pos)
|
||||
| None -> (
|
||||
match Desugared.Ast.IdentMap.find_opt ident ctxt.enum_idmap with
|
||||
| Some e_uid -> (Scopelang.Ast.TEnum e_uid, typ_pos)
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
"Unknown type, not a struct or enum previously declared" typ_pos ) ) )
|
||||
|
||||
let process_type ((typ, typ_pos) : Catala_ast.typ Pos.marked) : Lambda_ast.typ =
|
||||
(** Process a type (function or not) *)
|
||||
let process_type (ctxt : context) ((typ, typ_pos) : Ast.typ Pos.marked) :
|
||||
Scopelang.Ast.typ Pos.marked =
|
||||
match typ with
|
||||
| Catala_ast.Base base_typ -> process_base_typ (base_typ, typ_pos)
|
||||
| Catala_ast.Func { arg_typ; return_typ } ->
|
||||
Lambda_ast.TArrow (process_base_typ arg_typ, process_base_typ return_typ)
|
||||
| Ast.Base base_typ -> process_base_typ ctxt (base_typ, typ_pos)
|
||||
| Ast.Func { arg_typ; return_typ } ->
|
||||
( Scopelang.Ast.TArrow (process_base_typ ctxt arg_typ, process_base_typ ctxt return_typ),
|
||||
typ_pos )
|
||||
|
||||
(** Process data declaration *)
|
||||
let process_data_decl (scope : Uid.Scope.t) (ctxt : context)
|
||||
(decl : Catala_ast.scope_decl_context_data) : context =
|
||||
let process_data_decl (scope : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
(decl : Ast.scope_decl_context_data) : context =
|
||||
(* First check the type of the context data *)
|
||||
let data_typ = process_type decl.scope_decl_context_item_typ in
|
||||
let data_typ = process_type ctxt decl.scope_decl_context_item_typ in
|
||||
let is_cond = is_type_cond decl.scope_decl_context_item_typ in
|
||||
let name, pos = decl.scope_decl_context_item_name in
|
||||
let scope_ctxt = Uid.ScopeMap.find scope ctxt.scopes in
|
||||
match Uid.IdentMap.find_opt name scope_ctxt.var_idmap with
|
||||
let scope_ctxt = Scopelang.Ast.ScopeMap.find scope ctxt.scopes in
|
||||
match Desugared.Ast.IdentMap.find_opt name scope_ctxt.var_idmap with
|
||||
| Some use ->
|
||||
Errors.raise_multispanned_error "var name already used"
|
||||
[ (Some "first use", Pos.get_position (Uid.Var.get_info use)); (Some "second use", pos) ]
|
||||
[
|
||||
(Some "first use", Pos.get_position (Scopelang.Ast.ScopeVar.get_info use));
|
||||
(Some "second use", pos);
|
||||
]
|
||||
| None ->
|
||||
let uid = Uid.Var.fresh (name, pos) in
|
||||
let uid = Scopelang.Ast.ScopeVar.fresh (name, pos) in
|
||||
let scope_ctxt =
|
||||
{ scope_ctxt with var_idmap = Uid.IdentMap.add name uid scope_ctxt.var_idmap }
|
||||
{ scope_ctxt with var_idmap = Desugared.Ast.IdentMap.add name uid scope_ctxt.var_idmap }
|
||||
in
|
||||
{
|
||||
ctxt with
|
||||
scopes = Uid.ScopeMap.add scope scope_ctxt ctxt.scopes;
|
||||
var_typs = Uid.VarMap.add uid data_typ ctxt.var_typs;
|
||||
scopes = Scopelang.Ast.ScopeMap.add scope scope_ctxt ctxt.scopes;
|
||||
var_typs = Scopelang.Ast.ScopeVarMap.add uid (data_typ, is_cond) ctxt.var_typs;
|
||||
}
|
||||
|
||||
(** Process an item declaration *)
|
||||
let process_item_decl (scope : Uid.Scope.t) (ctxt : context)
|
||||
(decl : Catala_ast.scope_decl_context_item) : context =
|
||||
let process_item_decl (scope : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
(decl : Ast.scope_decl_context_item) : context =
|
||||
match decl with
|
||||
| Catala_ast.ContextData data_decl -> process_data_decl scope ctxt data_decl
|
||||
| Catala_ast.ContextScope sub_decl -> process_subscope_decl scope ctxt sub_decl
|
||||
| Ast.ContextData data_decl -> process_data_decl scope ctxt data_decl
|
||||
| Ast.ContextScope sub_decl -> process_subscope_decl scope ctxt sub_decl
|
||||
|
||||
(** Adds a binding to the context *)
|
||||
let add_def_local_var (ctxt : context) (scope_uid : Uid.Scope.t) (def_uid : Uid.ScopeDef.t)
|
||||
(name : ident Pos.marked) : context =
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
let def_ctx = Uid.ScopeDefMap.find def_uid scope_ctxt.definitions in
|
||||
let local_var_uid = Uid.LocalVar.fresh name in
|
||||
let def_ctx =
|
||||
{ var_idmap = Uid.IdentMap.add (Pos.unmark name) local_var_uid def_ctx.var_idmap }
|
||||
let add_def_local_var (ctxt : context) (name : ident Pos.marked) : context * Scopelang.Ast.Var.t =
|
||||
let local_var_uid = Scopelang.Ast.Var.make name in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
local_var_idmap =
|
||||
Desugared.Ast.IdentMap.add (Pos.unmark name) local_var_uid ctxt.local_var_idmap;
|
||||
}
|
||||
in
|
||||
let scope_ctxt =
|
||||
{ scope_ctxt with definitions = Uid.ScopeDefMap.add def_uid def_ctx scope_ctxt.definitions }
|
||||
in
|
||||
{ ctxt with scopes = Uid.ScopeMap.add scope_uid scope_ctxt ctxt.scopes }
|
||||
(ctxt, local_var_uid)
|
||||
|
||||
(** Process a scope declaration *)
|
||||
let process_scope_decl (ctxt : context) (decl : Catala_ast.scope_decl) : context =
|
||||
let process_scope_decl (ctxt : context) (decl : Ast.scope_decl) : context =
|
||||
let name, pos = decl.scope_decl_name in
|
||||
(* Checks if the name is already used *)
|
||||
match Uid.IdentMap.find_opt name ctxt.scope_idmap with
|
||||
match Desugared.Ast.IdentMap.find_opt name ctxt.scope_idmap with
|
||||
| Some use ->
|
||||
Errors.raise_multispanned_error "scope name already used"
|
||||
[ (Some "first use", Pos.get_position (Uid.Scope.get_info use)); (Some "second use", pos) ]
|
||||
[
|
||||
(Some "first use", Pos.get_position (Scopelang.Ast.ScopeName.get_info use));
|
||||
(Some "second use", pos);
|
||||
]
|
||||
| None ->
|
||||
let scope_uid = Uid.Scope.fresh (name, pos) in
|
||||
let scope_uid = Scopelang.Ast.ScopeName.fresh (name, pos) in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
scope_idmap = Uid.IdentMap.add name scope_uid ctxt.scope_idmap;
|
||||
scope_idmap = Desugared.Ast.IdentMap.add name scope_uid ctxt.scope_idmap;
|
||||
scopes =
|
||||
Uid.ScopeMap.add scope_uid
|
||||
Scopelang.Ast.ScopeMap.add scope_uid
|
||||
{
|
||||
var_idmap = Uid.IdentMap.empty;
|
||||
sub_scopes_idmap = Uid.IdentMap.empty;
|
||||
definitions = Uid.ScopeDefMap.empty;
|
||||
sub_scopes = Uid.SubScopeMap.empty;
|
||||
var_idmap = Desugared.Ast.IdentMap.empty;
|
||||
label_idmap = Desugared.Ast.IdentMap.empty;
|
||||
sub_scopes_idmap = Desugared.Ast.IdentMap.empty;
|
||||
sub_scopes = Scopelang.Ast.SubScopeMap.empty;
|
||||
}
|
||||
ctxt.scopes;
|
||||
}
|
||||
@ -169,137 +280,237 @@ let process_scope_decl (ctxt : context) (decl : Catala_ast.scope_decl) : context
|
||||
(fun ctxt item -> process_item_decl scope_uid ctxt (Pos.unmark item))
|
||||
ctxt decl.scope_decl_context
|
||||
|
||||
let qident_to_scope_def (ctxt : context) (scope_uid : Uid.Scope.t)
|
||||
(id : Catala_ast.qident Pos.marked) : Uid.ScopeDef.t =
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
match Pos.unmark id with
|
||||
| [ x ] -> (
|
||||
match Uid.IdentMap.find_opt (Pos.unmark x) scope_ctxt.var_idmap with
|
||||
| None -> raise_unknown_identifier "for a var of the scope" x
|
||||
| Some id -> Uid.ScopeDef.Var id )
|
||||
| [ s; x ] -> (
|
||||
let sub_scope_uid =
|
||||
match Uid.IdentMap.find_opt (Pos.unmark s) scope_ctxt.sub_scopes_idmap with
|
||||
| None -> raise_unknown_identifier "for a subscope of this scope" s
|
||||
| Some id -> id
|
||||
in
|
||||
let real_sub_scope_uid = Uid.SubScopeMap.find sub_scope_uid scope_ctxt.sub_scopes in
|
||||
let sub_scope_ctx = Uid.ScopeMap.find real_sub_scope_uid ctxt.scopes in
|
||||
match Uid.IdentMap.find_opt (Pos.unmark x) sub_scope_ctx.var_idmap with
|
||||
| None -> raise_unknown_identifier "for a var of this subscope" x
|
||||
| Some id -> Uid.ScopeDef.SubScopeVar (sub_scope_uid, id) )
|
||||
| _ -> raise_unsupported_feature "wrong qident" (Pos.get_position id)
|
||||
|
||||
let process_scope_use (ctxt : context) (use : Catala_ast.scope_use) : context =
|
||||
let scope_uid =
|
||||
match Uid.IdentMap.find_opt (Pos.unmark use.scope_use_name) ctxt.scope_idmap with
|
||||
| None -> raise_unknown_identifier "for a scope" use.scope_use_name
|
||||
| Some id -> id
|
||||
(** Process a struct declaration *)
|
||||
let process_struct_decl (ctxt : context) (sdecl : Ast.struct_decl) : context =
|
||||
let s_uid = Scopelang.Ast.StructName.fresh sdecl.struct_decl_name in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
struct_idmap =
|
||||
Desugared.Ast.IdentMap.add (Pos.unmark sdecl.struct_decl_name) s_uid ctxt.struct_idmap;
|
||||
}
|
||||
in
|
||||
List.fold_left
|
||||
(fun ctxt use_item ->
|
||||
match Pos.unmark use_item with
|
||||
| Catala_ast.Definition def ->
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
let def_uid = qident_to_scope_def ctxt scope_uid def.definition_name in
|
||||
let def_ctxt =
|
||||
{
|
||||
var_idmap =
|
||||
( match def.definition_parameter with
|
||||
| None -> Uid.IdentMap.empty
|
||||
| Some param -> Uid.IdentMap.singleton (Pos.unmark param) (Uid.LocalVar.fresh param)
|
||||
);
|
||||
}
|
||||
in
|
||||
let scope_ctxt =
|
||||
{
|
||||
scope_ctxt with
|
||||
definitions = Uid.ScopeDefMap.add def_uid def_ctxt scope_ctxt.definitions;
|
||||
}
|
||||
in
|
||||
{ ctxt with scopes = Uid.ScopeMap.add scope_uid scope_ctxt ctxt.scopes }
|
||||
| _ -> raise_unsupported_feature "unsupported item" (Pos.get_position use_item))
|
||||
ctxt use.scope_use_items
|
||||
(fun ctxt (fdecl, _) ->
|
||||
let f_uid = Scopelang.Ast.StructFieldName.fresh fdecl.Ast.struct_decl_field_name in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
field_idmap =
|
||||
Desugared.Ast.IdentMap.update
|
||||
(Pos.unmark fdecl.Ast.struct_decl_field_name)
|
||||
(fun uids ->
|
||||
match uids with
|
||||
| None -> Some (Scopelang.Ast.StructMap.singleton s_uid f_uid)
|
||||
| Some uids -> Some (Scopelang.Ast.StructMap.add s_uid f_uid uids))
|
||||
ctxt.field_idmap;
|
||||
}
|
||||
in
|
||||
{
|
||||
ctxt with
|
||||
structs =
|
||||
Scopelang.Ast.StructMap.update s_uid
|
||||
(fun fields ->
|
||||
match fields with
|
||||
| None ->
|
||||
Some
|
||||
(Scopelang.Ast.StructFieldMap.singleton f_uid
|
||||
(process_type ctxt fdecl.Ast.struct_decl_field_typ))
|
||||
| Some fields ->
|
||||
Some
|
||||
(Scopelang.Ast.StructFieldMap.add f_uid
|
||||
(process_type ctxt fdecl.Ast.struct_decl_field_typ)
|
||||
fields))
|
||||
ctxt.structs;
|
||||
})
|
||||
ctxt sdecl.struct_decl_fields
|
||||
|
||||
(** Process a code item : for now it only handles scope decls *)
|
||||
let process_use_item (ctxt : context) (item : Catala_ast.code_item Pos.marked) : context =
|
||||
(** Process an enum declaration *)
|
||||
let process_enum_decl (ctxt : context) (edecl : Ast.enum_decl) : context =
|
||||
let e_uid = Scopelang.Ast.EnumName.fresh edecl.enum_decl_name in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
enum_idmap =
|
||||
Desugared.Ast.IdentMap.add (Pos.unmark edecl.enum_decl_name) e_uid ctxt.enum_idmap;
|
||||
}
|
||||
in
|
||||
List.fold_left
|
||||
(fun ctxt (cdecl, cdecl_pos) ->
|
||||
let c_uid = Scopelang.Ast.EnumConstructor.fresh cdecl.Ast.enum_decl_case_name in
|
||||
let ctxt =
|
||||
{
|
||||
ctxt with
|
||||
constructor_idmap =
|
||||
Desugared.Ast.IdentMap.update
|
||||
(Pos.unmark cdecl.Ast.enum_decl_case_name)
|
||||
(fun uids ->
|
||||
match uids with
|
||||
| None -> Some (Scopelang.Ast.EnumMap.singleton e_uid c_uid)
|
||||
| Some uids -> Some (Scopelang.Ast.EnumMap.add e_uid c_uid uids))
|
||||
ctxt.constructor_idmap;
|
||||
}
|
||||
in
|
||||
{
|
||||
ctxt with
|
||||
enums =
|
||||
Scopelang.Ast.EnumMap.update e_uid
|
||||
(fun cases ->
|
||||
let typ =
|
||||
match cdecl.Ast.enum_decl_case_typ with
|
||||
| None -> (Scopelang.Ast.TLit TUnit, cdecl_pos)
|
||||
| Some typ -> process_type ctxt typ
|
||||
in
|
||||
match cases with
|
||||
| None -> Some (Scopelang.Ast.EnumConstructorMap.singleton c_uid typ)
|
||||
| Some fields -> Some (Scopelang.Ast.EnumConstructorMap.add c_uid typ fields))
|
||||
ctxt.enums;
|
||||
})
|
||||
ctxt edecl.enum_decl_cases
|
||||
|
||||
(** Process a code item that is a declaration *)
|
||||
let process_decl_item (ctxt : context) (item : Ast.code_item Pos.marked) : context =
|
||||
match Pos.unmark item with
|
||||
| ScopeDecl _ -> ctxt
|
||||
| ScopeUse use -> process_scope_use ctxt use
|
||||
| _ -> raise_unsupported_feature "item not supported" (Pos.get_position item)
|
||||
|
||||
(** Process a code item : for now it only handles scope decls *)
|
||||
let process_decl_item (ctxt : context) (item : Catala_ast.code_item Pos.marked) : context =
|
||||
match Pos.unmark item with ScopeDecl decl -> process_scope_decl ctxt decl | _ -> ctxt
|
||||
| ScopeDecl decl -> process_scope_decl ctxt decl
|
||||
| StructDecl sdecl -> process_struct_decl ctxt sdecl
|
||||
| EnumDecl edecl -> process_enum_decl ctxt edecl
|
||||
| ScopeUse _ -> ctxt
|
||||
|
||||
(** Process a code block *)
|
||||
let process_code_block (ctxt : context) (block : Catala_ast.code_block)
|
||||
(process_item : context -> Catala_ast.code_item Pos.marked -> context) : context =
|
||||
let process_code_block (ctxt : context) (block : Ast.code_block)
|
||||
(process_item : context -> Ast.code_item Pos.marked -> context) : context =
|
||||
List.fold_left (fun ctxt decl -> process_item ctxt decl) ctxt block
|
||||
|
||||
(** Process a program item *)
|
||||
let process_law_article_item (ctxt : context) (item : Catala_ast.law_article_item)
|
||||
(process_item : context -> Catala_ast.code_item Pos.marked -> context) : context =
|
||||
(** Process a law article item, only considering the code blocks *)
|
||||
let process_law_article_item (ctxt : context) (item : Ast.law_article_item)
|
||||
(process_item : context -> Ast.code_item Pos.marked -> context) : context =
|
||||
match item with CodeBlock (block, _) -> process_code_block ctxt block process_item | _ -> ctxt
|
||||
|
||||
(** Process a law structure *)
|
||||
let rec process_law_structure (ctxt : context) (s : Catala_ast.law_structure)
|
||||
(process_item : context -> Catala_ast.code_item Pos.marked -> context) : context =
|
||||
(** Process a law structure, only considering the code blocks *)
|
||||
let rec process_law_structure (ctxt : context) (s : Ast.law_structure)
|
||||
(process_item : context -> Ast.code_item Pos.marked -> context) : context =
|
||||
match s with
|
||||
| Catala_ast.LawHeading (_, children) ->
|
||||
| Ast.LawHeading (_, children) ->
|
||||
List.fold_left (fun ctxt child -> process_law_structure ctxt child process_item) ctxt children
|
||||
| Catala_ast.LawArticle (_, children) ->
|
||||
| Ast.LawArticle (_, children) ->
|
||||
List.fold_left
|
||||
(fun ctxt child -> process_law_article_item ctxt child process_item)
|
||||
ctxt children
|
||||
| Catala_ast.MetadataBlock (b, c) ->
|
||||
process_law_article_item ctxt (Catala_ast.CodeBlock (b, c)) process_item
|
||||
| Catala_ast.IntermediateText _ -> ctxt
|
||||
| Ast.MetadataBlock (b, c) -> process_law_article_item ctxt (Ast.CodeBlock (b, c)) process_item
|
||||
| Ast.IntermediateText _ | Ast.LawInclude _ -> ctxt
|
||||
|
||||
(** Process a program item *)
|
||||
let process_program_item (ctxt : context) (item : Catala_ast.program_item)
|
||||
(process_item : context -> Catala_ast.code_item Pos.marked -> context) : context =
|
||||
match item with Catala_ast.LawStructure s -> process_law_structure ctxt s process_item
|
||||
(** Process a program item, only considering the code blocks *)
|
||||
let process_program_item (ctxt : context) (item : Ast.program_item)
|
||||
(process_item : context -> Ast.code_item Pos.marked -> context) : context =
|
||||
match item with Ast.LawStructure s -> process_law_structure ctxt s process_item
|
||||
|
||||
(** Derive the context from metadata, in two passes *)
|
||||
let form_context (prgm : Catala_ast.program) : context =
|
||||
(** {1 Scope uses pass} *)
|
||||
|
||||
let process_rule (ctxt : context) (s_name : Scopelang.Ast.ScopeName.t) (r : Ast.rule) : context =
|
||||
match r.Ast.rule_label with
|
||||
| None -> ctxt
|
||||
| Some label ->
|
||||
let rule_name =
|
||||
Desugared.Ast.RuleName.fresh
|
||||
(Pos.map_under_mark
|
||||
(fun qident -> String.concat "." (List.map (fun i -> Pos.unmark i) qident))
|
||||
r.rule_name)
|
||||
in
|
||||
{
|
||||
ctxt with
|
||||
scopes =
|
||||
Scopelang.Ast.ScopeMap.update s_name
|
||||
(fun s_ctxt ->
|
||||
match s_ctxt with
|
||||
| None -> assert false (* should not happen *)
|
||||
| Some s_ctxt ->
|
||||
Some
|
||||
{
|
||||
s_ctxt with
|
||||
label_idmap =
|
||||
Desugared.Ast.IdentMap.add (Pos.unmark label) rule_name s_ctxt.label_idmap;
|
||||
})
|
||||
ctxt.scopes;
|
||||
}
|
||||
|
||||
let process_definition (ctxt : context) (s_name : Scopelang.Ast.ScopeName.t) (d : Ast.definition) :
|
||||
context =
|
||||
match d.Ast.definition_label with
|
||||
| None -> ctxt
|
||||
| Some label ->
|
||||
let definition_name =
|
||||
Desugared.Ast.RuleName.fresh
|
||||
(Pos.map_under_mark
|
||||
(fun qident -> String.concat "." (List.map (fun i -> Pos.unmark i) qident))
|
||||
d.definition_name)
|
||||
in
|
||||
{
|
||||
ctxt with
|
||||
scopes =
|
||||
Scopelang.Ast.ScopeMap.update s_name
|
||||
(fun s_ctxt ->
|
||||
match s_ctxt with
|
||||
| None -> assert false (* should not happen *)
|
||||
| Some s_ctxt ->
|
||||
Some
|
||||
{
|
||||
s_ctxt with
|
||||
label_idmap =
|
||||
Desugared.Ast.IdentMap.add (Pos.unmark label) definition_name
|
||||
s_ctxt.label_idmap;
|
||||
})
|
||||
ctxt.scopes;
|
||||
}
|
||||
|
||||
let process_scope_use_item (s_name : Scopelang.Ast.ScopeName.t) (ctxt : context)
|
||||
(sitem : Ast.scope_use_item Pos.marked) : context =
|
||||
match Pos.unmark sitem with
|
||||
| Rule r -> process_rule ctxt s_name r
|
||||
| Definition d -> process_definition ctxt s_name d
|
||||
| _ -> ctxt
|
||||
|
||||
let process_scope_use (ctxt : context) (suse : Ast.scope_use) : context =
|
||||
let s_name =
|
||||
try Desugared.Ast.IdentMap.find (Pos.unmark suse.Ast.scope_use_name) ctxt.scope_idmap
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "\"%s\": this scope has not been declared anywhere, is it a typo?"
|
||||
(Pos.unmark suse.Ast.scope_use_name))
|
||||
(Pos.get_position suse.Ast.scope_use_name)
|
||||
in
|
||||
List.fold_left (process_scope_use_item s_name) ctxt suse.Ast.scope_use_items
|
||||
|
||||
let process_use_item (ctxt : context) (item : Ast.code_item Pos.marked) : context =
|
||||
match Pos.unmark item with
|
||||
| ScopeDecl _ | StructDecl _ | EnumDecl _ -> ctxt
|
||||
| ScopeUse suse -> process_scope_use ctxt suse
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
(** Derive the context from metadata, in one pass over the declarations *)
|
||||
let form_context (prgm : Ast.program) : context =
|
||||
let empty_ctxt =
|
||||
{ scope_idmap = Uid.IdentMap.empty; scopes = Uid.ScopeMap.empty; var_typs = Uid.VarMap.empty }
|
||||
{
|
||||
local_var_idmap = Desugared.Ast.IdentMap.empty;
|
||||
scope_idmap = Desugared.Ast.IdentMap.empty;
|
||||
scopes = Scopelang.Ast.ScopeMap.empty;
|
||||
var_typs = Scopelang.Ast.ScopeVarMap.empty;
|
||||
structs = Scopelang.Ast.StructMap.empty;
|
||||
struct_idmap = Desugared.Ast.IdentMap.empty;
|
||||
field_idmap = Desugared.Ast.IdentMap.empty;
|
||||
enums = Scopelang.Ast.EnumMap.empty;
|
||||
enum_idmap = Desugared.Ast.IdentMap.empty;
|
||||
constructor_idmap = Desugared.Ast.IdentMap.empty;
|
||||
}
|
||||
in
|
||||
let ctxt =
|
||||
List.fold_left
|
||||
(fun ctxt item -> process_program_item ctxt item process_decl_item)
|
||||
empty_ctxt prgm.program_items
|
||||
in
|
||||
List.fold_left
|
||||
(fun ctxt item -> process_program_item ctxt item process_use_item)
|
||||
ctxt prgm.program_items
|
||||
|
||||
(** Get the variable uid inside the scope given in argument *)
|
||||
let get_var_uid (scope_uid : Uid.Scope.t) (ctxt : context) ((x, pos) : ident Pos.marked) : Uid.Var.t
|
||||
=
|
||||
let scope = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
match Uid.IdentMap.find_opt x scope.var_idmap with
|
||||
| None -> raise_unknown_identifier "for a var of this scope" (x, pos)
|
||||
| Some uid -> uid
|
||||
|
||||
(** Get the subscope uid inside the scope given in argument *)
|
||||
let get_subscope_uid (scope_uid : Uid.Scope.t) (ctxt : context) ((y, pos) : ident Pos.marked) :
|
||||
Uid.SubScope.t =
|
||||
let scope = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
match Uid.IdentMap.find_opt y scope.sub_scopes_idmap with
|
||||
| None -> raise_unknown_identifier "for a subscope of this scope" (y, pos)
|
||||
| Some sub_uid -> sub_uid
|
||||
|
||||
(** Checks if the var_uid belongs to the scope scope_uid *)
|
||||
let belongs_to (ctxt : context) (uid : Uid.Var.t) (scope_uid : Uid.Scope.t) : bool =
|
||||
let scope = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
Uid.IdentMap.exists (fun _ var_uid -> Uid.Var.compare uid var_uid = 0) scope.var_idmap
|
||||
|
||||
let get_def_typ (ctxt : context) (def : Uid.ScopeDef.t) : typ =
|
||||
match def with
|
||||
| Uid.ScopeDef.SubScopeVar (_, x)
|
||||
(* we don't need to look at the subscope prefix because [x] is already the uid referring back to
|
||||
the original subscope *)
|
||||
| Uid.ScopeDef.Var x ->
|
||||
Uid.VarMap.find x ctxt.var_typs
|
||||
let ctxt =
|
||||
List.fold_left
|
||||
(fun ctxt item -> process_program_item ctxt item process_use_item)
|
||||
ctxt prgm.program_items
|
||||
in
|
||||
ctxt
|
||||
|
@ -17,7 +17,9 @@
|
||||
*)
|
||||
|
||||
%{
|
||||
open Catala_ast
|
||||
open Ast
|
||||
|
||||
module Errors = Utils.Errors
|
||||
|
||||
type struct_or_enum_inject_content =
|
||||
| StructContent of (ident Pos.marked * expression Pos.marked) list
|
||||
@ -28,32 +30,42 @@
|
||||
%token EOF
|
||||
%token<string * string option * string option> LAW_ARTICLE
|
||||
%token<string * int> LAW_HEADING
|
||||
%token<Catala_ast.law_include> LAW_INCLUDE
|
||||
%token<Ast.law_include> LAW_INCLUDE
|
||||
%token<string> LAW_TEXT
|
||||
%token<string> CONSTRUCTOR IDENT
|
||||
%token<string> END_CODE
|
||||
%token<int> INT_LITERAL
|
||||
%token<Z.t> INT_LITERAL
|
||||
%token TRUE FALSE
|
||||
%token<int * int> DECIMAL_LITERAL
|
||||
%token<int * int> MONEY_AMOUNT
|
||||
%token<Z.t * Z.t> DECIMAL_LITERAL
|
||||
%token<Z.t * Z.t> MONEY_AMOUNT
|
||||
%token BEGIN_CODE TEXT MASTER_FILE
|
||||
%token COLON ALT DATA VERTICAL
|
||||
%token OF INTEGER COLLECTION
|
||||
%token RULE CONDITION DEFINED_AS
|
||||
%token EXISTS IN SUCH THAT NOW LESSER GREATER
|
||||
%token DOT AND OR LPAREN RPAREN OPTIONAL EQUAL
|
||||
%token CARDINAL LESSER_EQUAL GREATER_EQUAL
|
||||
%token ASSERTION FIXED BY YEAR
|
||||
%token PLUS MINUS MULT DIV MATCH WITH VARIES WITH_V
|
||||
%token LESSER GREATER LESSER_EQUAL GREATER_EQUAL
|
||||
%token LESSER_DEC GREATER_DEC LESSER_EQUAL_DEC GREATER_EQUAL_DEC
|
||||
%token LESSER_MONEY GREATER_MONEY LESSER_EQUAL_MONEY GREATER_EQUAL_MONEY
|
||||
%token LESSER_DATE GREATER_DATE LESSER_EQUAL_DATE GREATER_EQUAL_DATE
|
||||
%token LESSER_DURATION GREATER_DURATION LESSER_EQUAL_DURATION GREATER_EQUAL_DURATION
|
||||
%token EXISTS IN SUCH THAT
|
||||
%token DOT AND OR LPAREN RPAREN EQUAL
|
||||
%token CARDINAL ASSERTION FIXED BY YEAR MONTH DAY
|
||||
%token PLUS MINUS MULT DIV
|
||||
%token PLUSDEC MINUSDEC MULTDEC DIVDEC
|
||||
%token PLUSMONEY MINUSMONEY MULTMONEY DIVMONEY
|
||||
%token MINUSDATE PLUSDATE PLUSDURATION MINUSDURATION
|
||||
%token MATCH WITH VARIES WITH_V
|
||||
%token FOR ALL WE_HAVE INCREASING DECREASING
|
||||
%token NOT BOOLEAN PERCENT ARROW
|
||||
%token NOT BOOLEAN PERCENT ARROW DURATION
|
||||
%token SCOPE FILLED NOT_EQUAL DEFINITION
|
||||
%token STRUCT CONTENT IF THEN DEPENDS DECLARATION
|
||||
%token CONTEXT ENUM ELSE DATE SUM
|
||||
%token BEGIN_METADATA END_METADATA MONEY DECIMAL
|
||||
%token UNDER_CONDITION CONSEQUENCE
|
||||
%token UNDER_CONDITION CONSEQUENCE LBRACKET RBRACKET
|
||||
%token LABEL EXCEPTION LSQUARE RSQUARE SEMICOLON
|
||||
%token INT_TO_DEC MAXIMUM MINIMUM
|
||||
|
||||
%type <Catala_ast.source_file_or_master> source_file_or_master
|
||||
%type <Ast.source_file_or_master> source_file_or_master
|
||||
|
||||
%start source_file_or_master
|
||||
|
||||
@ -63,6 +75,7 @@ typ_base:
|
||||
| INTEGER { (Integer, $sloc) }
|
||||
| BOOLEAN { (Boolean, $sloc) }
|
||||
| MONEY { (Money, $sloc) }
|
||||
| DURATION { (Duration, $sloc) }
|
||||
| TEXT { (Text, $sloc) }
|
||||
| DECIMAL { (Decimal, $sloc) }
|
||||
| DATE { (Date, $sloc) }
|
||||
@ -74,18 +87,12 @@ typ_base:
|
||||
collection_marked:
|
||||
| COLLECTION { $sloc }
|
||||
|
||||
optional_marked:
|
||||
| OPTIONAL { $sloc }
|
||||
|
||||
typ:
|
||||
| t = typ_base {
|
||||
let t, loc = t in
|
||||
(Primitive t, loc)
|
||||
}
|
||||
| collection_marked t = typ {
|
||||
(Optional t, $sloc)
|
||||
}
|
||||
| optional_marked t = typ {
|
||||
(Collection t, $sloc)
|
||||
}
|
||||
|
||||
@ -118,27 +125,31 @@ enum_inject_content:
|
||||
|
||||
struct_or_enum_inject_content:
|
||||
| e = option(enum_inject_content) { EnumContent e }
|
||||
| CONTENT LPAREN ALT fields = separated_nonempty_list(ALT, struct_content_field) RPAREN {
|
||||
| LBRACKET ALT fields = separated_nonempty_list(ALT, struct_content_field) RBRACKET {
|
||||
StructContent fields
|
||||
}
|
||||
|
||||
struct_or_enum_inject:
|
||||
| c = constructor data = struct_or_enum_inject_content {
|
||||
match data with
|
||||
| EnumContent data ->
|
||||
(EnumInject (c, data), $sloc)
|
||||
| _ -> assert false
|
||||
| EnumContent data -> (EnumInject (c, data), $sloc)
|
||||
| StructContent fields -> (StructLit (c, fields), $sloc)
|
||||
}
|
||||
|
||||
primitive_expression:
|
||||
| e = small_expression { e }
|
||||
| NOW { (Builtin Now, $sloc) }
|
||||
| CARDINAL {
|
||||
(Builtin Cardinal, $sloc)
|
||||
}
|
||||
| INT_TO_DEC {
|
||||
(Builtin IntToDec, $sloc)
|
||||
}
|
||||
| e = struct_or_enum_inject {
|
||||
e
|
||||
}
|
||||
| LSQUARE l = separated_list(SEMICOLON, expression) RSQUARE {
|
||||
(ArrayLit l, $sloc)
|
||||
}
|
||||
|
||||
num_literal:
|
||||
| d = INT_LITERAL { (Int d, $sloc) }
|
||||
@ -150,9 +161,11 @@ num_literal:
|
||||
unit_literal:
|
||||
| PERCENT { (Percent, $sloc) }
|
||||
| YEAR { (Year, $sloc)}
|
||||
| MONTH { (Month, $sloc) }
|
||||
| DAY { (Day, $sloc) }
|
||||
|
||||
date_int:
|
||||
| d = INT_LITERAL { (d, $sloc) }
|
||||
| d = INT_LITERAL { (Z.to_int d, $sloc) }
|
||||
|
||||
literal:
|
||||
| l = num_literal u = option(unit_literal) {
|
||||
@ -176,15 +189,33 @@ literal:
|
||||
| FALSE { (Bool false, $sloc) }
|
||||
|
||||
compare_op:
|
||||
| LESSER { (Lt, $sloc) }
|
||||
| LESSER_EQUAL { (Lte, $sloc) }
|
||||
| GREATER { (Gt, $sloc) }
|
||||
| GREATER_EQUAL { (Gte, $sloc) }
|
||||
| LESSER { (Lt KInt, $sloc) }
|
||||
| LESSER_EQUAL { (Lte KInt, $sloc) }
|
||||
| GREATER { (Gt KInt, $sloc) }
|
||||
| GREATER_EQUAL { (Gte KInt, $sloc) }
|
||||
| LESSER_DEC { (Lt KDec, $sloc) }
|
||||
| LESSER_EQUAL_DEC { (Lte KDec, $sloc) }
|
||||
| GREATER_DEC { (Gt KDec, $sloc) }
|
||||
| GREATER_EQUAL_DEC { (Gte KDec, $sloc) }
|
||||
| LESSER_MONEY { (Lt KMoney, $sloc) }
|
||||
| LESSER_EQUAL_MONEY { (Lte KMoney, $sloc) }
|
||||
| GREATER_MONEY { (Gt KMoney, $sloc) }
|
||||
| GREATER_EQUAL_MONEY { (Gte KMoney, $sloc) }
|
||||
| LESSER_DATE { (Lt KDate, $sloc) }
|
||||
| LESSER_EQUAL_DATE { (Lte KDate, $sloc) }
|
||||
| GREATER_DATE { (Gt KDate, $sloc) }
|
||||
| GREATER_EQUAL_DATE { (Gte KDate, $sloc) }
|
||||
| LESSER_DURATION { (Lt KDuration, $sloc) }
|
||||
| LESSER_EQUAL_DURATION { (Lte KDuration, $sloc) }
|
||||
| GREATER_DURATION { (Gt KDuration, $sloc) }
|
||||
| GREATER_EQUAL_DURATION { (Gte KDuration, $sloc) }
|
||||
| EQUAL { (Eq, $sloc) }
|
||||
| NOT_EQUAL { (Neq, $sloc) }
|
||||
|
||||
aggregate_func:
|
||||
| SUM { (Aggregate AggregateSum, $sloc) }
|
||||
| MAXIMUM t = typ_base { (Aggregate (AggregateExtremum (true, Pos.unmark t)), $sloc) }
|
||||
| MINIMUM t = typ_base { (Aggregate (AggregateExtremum (false, Pos.unmark t)), $sloc) }
|
||||
| SUM t = typ_base { (Aggregate (AggregateSum (Pos.unmark t)), $sloc) }
|
||||
| CARDINAL { (Aggregate AggregateCount, $sloc) }
|
||||
|
||||
aggregate:
|
||||
@ -207,8 +238,12 @@ base_expression:
|
||||
}
|
||||
|
||||
mult_op:
|
||||
| MULT { (Mult, $sloc) }
|
||||
| DIV { (Div, $sloc) }
|
||||
| MULT { (Mult KInt, $sloc) }
|
||||
| DIV { (Div KInt, $sloc) }
|
||||
| MULTDEC { (Mult KDec, $sloc) }
|
||||
| DIVDEC { (Div KDec, $sloc) }
|
||||
| MULTMONEY { (Mult KMoney, $sloc) }
|
||||
| DIVMONEY { (Div KMoney, $sloc) }
|
||||
|
||||
mult_expression:
|
||||
| e = base_expression { e }
|
||||
@ -217,11 +252,22 @@ mult_expression:
|
||||
}
|
||||
|
||||
sum_op:
|
||||
| PLUS { (Add, $sloc) }
|
||||
| MINUS { (Sub, $sloc) }
|
||||
| PLUSDURATION { (Add KDuration, $sloc) }
|
||||
| MINUSDURATION { (Sub KDuration, $sloc) }
|
||||
| PLUSDATE { (Add KDate, $sloc) }
|
||||
| MINUSDATE { (Sub KDate, $sloc) }
|
||||
| PLUSMONEY { (Add KMoney, $sloc) }
|
||||
| MINUSMONEY { (Sub KMoney, $sloc) }
|
||||
| PLUSDEC { (Add KDec, $sloc) }
|
||||
| MINUSDEC { (Sub KDec, $sloc) }
|
||||
| PLUS { (Add KInt, $sloc) }
|
||||
| MINUS { (Sub KInt, $sloc) }
|
||||
|
||||
sum_unop:
|
||||
| MINUS { (Minus, $sloc) }
|
||||
| MINUS { (Minus KInt, $sloc) }
|
||||
| MINUSDEC { (Minus KDec, $sloc) }
|
||||
| MINUSMONEY { (Minus KMoney, $sloc) }
|
||||
| MINUSDURATION { (Minus KDuration, $sloc) }
|
||||
|
||||
sum_expression:
|
||||
| e = mult_expression { e }
|
||||
@ -328,11 +374,16 @@ rule_consequence:
|
||||
}
|
||||
|
||||
rule:
|
||||
| name_and_param = rule_expr cond = option(condition_consequence)
|
||||
| label = option(label)
|
||||
except = option(exception_to)
|
||||
RULE
|
||||
name_and_param = rule_expr cond = option(condition_consequence)
|
||||
consequence = rule_consequence {
|
||||
let (name, param_applied) = name_and_param in
|
||||
let cons : bool Pos.marked = consequence in
|
||||
({
|
||||
rule_label = label;
|
||||
rule_exception_to = except;
|
||||
rule_parameter = param_applied;
|
||||
rule_condition = cond;
|
||||
rule_name = name;
|
||||
@ -343,10 +394,21 @@ rule:
|
||||
definition_parameters:
|
||||
| OF i = ident { i }
|
||||
|
||||
label:
|
||||
| LABEL i = ident { i }
|
||||
|
||||
exception_to:
|
||||
| EXCEPTION i = ident { i }
|
||||
|
||||
definition:
|
||||
| name = qident param = option(definition_parameters)
|
||||
| label = option(label)
|
||||
except = option(exception_to)
|
||||
DEFINITION
|
||||
name = qident param = option(definition_parameters)
|
||||
cond = option(condition_consequence) DEFINED_AS e = expression {
|
||||
({
|
||||
definition_label = label;
|
||||
definition_exception_to = except;
|
||||
definition_name = name;
|
||||
definition_parameter = param;
|
||||
definition_condition = cond;
|
||||
@ -374,10 +436,10 @@ assertion:
|
||||
}
|
||||
|
||||
scope_item:
|
||||
| RULE r = rule {
|
||||
| r = rule {
|
||||
let (r, _) = r in (Rule r, $sloc)
|
||||
}
|
||||
| DEFINITION d = definition {
|
||||
| d = definition {
|
||||
let (d, _) = d in (Definition d, $sloc)
|
||||
}
|
||||
| ASSERTION contents = assertion {
|
||||
@ -410,9 +472,9 @@ struct_scope:
|
||||
struct_decl_field_name = name;
|
||||
struct_decl_field_typ = match func_typ with
|
||||
| None -> (Base typ, typ_pos)
|
||||
| Some (return_typ, return_pos) -> (Func {
|
||||
arg_typ = (typ, typ_pos);
|
||||
return_typ = (Data return_typ, return_pos);
|
||||
| Some (arg_typ, arg_pos) -> (Func {
|
||||
arg_typ = (Data arg_typ, arg_pos);
|
||||
return_typ = (typ, typ_pos);
|
||||
}, $sloc) ;
|
||||
}, $sloc)
|
||||
}
|
||||
@ -424,9 +486,9 @@ scope_decl_item:
|
||||
let (typ, typ_pos) = t in
|
||||
match func_typ with
|
||||
| None -> (Base (Data typ), typ_pos)
|
||||
| Some (return_typ, return_pos) -> (Func {
|
||||
arg_typ = (Data typ, typ_pos);
|
||||
return_typ = (Data return_typ, return_pos);
|
||||
| Some (arg_typ, arg_pos) -> (Func {
|
||||
arg_typ = (Data arg_typ, arg_pos);
|
||||
return_typ = (Data typ, typ_pos);
|
||||
}, $sloc);
|
||||
}), $sloc) }
|
||||
| CONTEXT i = ident SCOPE c = constructor {
|
||||
@ -440,9 +502,9 @@ scope_decl_item:
|
||||
scope_decl_context_item_typ =
|
||||
match func_typ with
|
||||
| None -> (Base (Condition), $loc(_condition))
|
||||
| Some (return_typ, return_pos) -> (Func {
|
||||
arg_typ = (Condition, $loc(_condition));
|
||||
return_typ = (Data return_typ, return_pos);
|
||||
| Some (arg_typ, arg_pos) -> (Func {
|
||||
arg_typ = (Data arg_typ, arg_pos);
|
||||
return_typ = (Condition, $loc(_condition));
|
||||
}, $sloc);
|
||||
}), $sloc) }
|
||||
|
||||
@ -492,20 +554,17 @@ code:
|
||||
| code = list(code_item) { (code, $sloc) }
|
||||
|
||||
metadata_block:
|
||||
| BEGIN_CODE code_and_pos = code text = END_CODE END_METADATA {
|
||||
| BEGIN_CODE option(law_text) code_and_pos = code text = END_CODE option(law_text) END_METADATA {
|
||||
let (code, pos) = code_and_pos in
|
||||
(code, (text, pos))
|
||||
}
|
||||
|
||||
law_article_item:
|
||||
| text = LAW_TEXT { LawText text }
|
||||
| text = law_text { LawText text }
|
||||
| BEGIN_CODE code_and_pos = code text = END_CODE {
|
||||
let (code, pos) = code_and_pos in
|
||||
CodeBlock (code, (text, pos))
|
||||
}
|
||||
| includ = LAW_INCLUDE {
|
||||
LawInclude includ
|
||||
}
|
||||
|
||||
law_article:
|
||||
| title = LAW_ARTICLE {
|
||||
@ -528,7 +587,11 @@ law_articles_items:
|
||||
| { [] }
|
||||
|
||||
law_text:
|
||||
| text = LAW_TEXT { LawStructure (IntermediateText text) }
|
||||
| text = LAW_TEXT { String.trim text }
|
||||
|
||||
law_intermediate_text:
|
||||
| text = law_text { LawStructure (IntermediateText text) }
|
||||
|
||||
|
||||
source_file_article:
|
||||
| article = law_article items = law_articles_items {
|
||||
@ -539,22 +602,25 @@ source_file_item:
|
||||
| heading = law_heading {
|
||||
LawStructure (LawHeading (heading, []))
|
||||
}
|
||||
| BEGIN_METADATA code = metadata_block {
|
||||
| BEGIN_METADATA option(law_text) code = metadata_block {
|
||||
let (code, source_repr) = code in
|
||||
LawStructure (MetadataBlock (code, source_repr))
|
||||
}
|
||||
| includ = LAW_INCLUDE {
|
||||
LawStructure (LawInclude includ)
|
||||
}
|
||||
|
||||
source_file_after_text:
|
||||
| i = source_file_article f = source_file_after_text {
|
||||
i::f
|
||||
}
|
||||
| i = source_file_item l = list(law_text) f = source_file_after_text {
|
||||
| i = source_file_item l = list(law_intermediate_text) f = source_file_after_text {
|
||||
i::l@f
|
||||
}
|
||||
| EOF { [] }
|
||||
|
||||
source_file:
|
||||
| l = list(law_text) f = source_file_after_text { l@f }
|
||||
| l = list(law_intermediate_text) f = source_file_after_text { l@f }
|
||||
|
||||
master_file_include:
|
||||
| includ = LAW_INCLUDE {
|
||||
@ -564,11 +630,11 @@ master_file_include:
|
||||
}
|
||||
|
||||
master_file_includes:
|
||||
| i = master_file_include is = master_file_includes { i::is }
|
||||
| i = master_file_include option(law_text) is = master_file_includes { i::is }
|
||||
| EOF { [] }
|
||||
|
||||
source_file_or_master:
|
||||
| MASTER_FILE is = master_file_includes { MasterFile is }
|
||||
| MASTER_FILE option(law_text) is = master_file_includes { MasterFile is }
|
||||
| f = source_file {
|
||||
(*
|
||||
now here the heading structure is completely flat because of the
|
||||
@ -585,7 +651,7 @@ source_file_or_master:
|
||||
| [] -> assert false (* there should be at least one rest element *)
|
||||
| rest_head::rest_tail ->
|
||||
begin match first_item with
|
||||
| LawStructure (LawArticle _ | MetadataBlock _ | IntermediateText _) ->
|
||||
| LawStructure (LawArticle _ | MetadataBlock _ | IntermediateText _ | LawInclude _) ->
|
||||
(* if an article or an include is just before a new heading or a new article,
|
||||
then we don't merge it with what comes next *)
|
||||
first_item::rest_head::rest_tail
|
||||
|
@ -12,17 +12,27 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Wrapping module around parser and lexer that offers the {!val: parse_source_file} API *)
|
||||
|
||||
open Sedlexing
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module Cli = Utils.Cli
|
||||
module I = Parser.MenhirInterpreter
|
||||
|
||||
(** {1 Internal functions} *)
|
||||
|
||||
(** Returns the state number from the Menhir environment *)
|
||||
let state (env : 'semantic_value I.env) : int =
|
||||
match Lazy.force (I.stack env) with
|
||||
| MenhirLib.General.Nil -> 0
|
||||
| MenhirLib.General.Cons (Element (s, _, _, _), _) -> I.number s
|
||||
|
||||
(** Computes the levenshtein distance between two strings *)
|
||||
(** Three-way minimum *)
|
||||
let minimum a b c = min a (min b c)
|
||||
|
||||
(** Computes the levenshtein distance between two strings, used to provide error messages
|
||||
suggestions *)
|
||||
let levenshtein_distance (s : string) (t : string) : int =
|
||||
let m = String.length s and n = String.length t in
|
||||
(* for all i and j, d.(i).(j) will hold the Levenshtein distance between the first i characters of
|
||||
@ -50,19 +60,33 @@ let levenshtein_distance (s : string) (t : string) : int =
|
||||
|
||||
d.(m).(n)
|
||||
|
||||
(** Style with which to display syntax hints in the terminal output *)
|
||||
let syntax_hints_style = [ ANSITerminal.yellow ]
|
||||
|
||||
(** Usage: [raise_parser_error error_loc last_good_loc token msg]
|
||||
|
||||
Raises an error message featuring the [error_loc] position where the parser has failed, the
|
||||
[token] on which the parser has failed, and the error message [msg]. If available, displays
|
||||
[last_good_loc] the location of the last token correctly parsed. *)
|
||||
let raise_parser_error (error_loc : Pos.t) (last_good_loc : Pos.t option) (token : string)
|
||||
(msg : string) : 'a =
|
||||
Errors.raise_multispanned_error
|
||||
(Printf.sprintf "Syntax error at token %s\n%s"
|
||||
(Cli.print_with_style syntax_hints_style "\"%s\"" token)
|
||||
msg)
|
||||
( ( match last_good_loc with
|
||||
| None -> []
|
||||
| Some last_good_loc -> [ (Some "Last good token:", last_good_loc) ] )
|
||||
@ [ (Some "Error token:", error_loc) ] )
|
||||
( (Some "Error token:", error_loc)
|
||||
::
|
||||
( match last_good_loc with
|
||||
| None -> []
|
||||
| Some last_good_loc -> [ (Some "Last good token:", last_good_loc) ] ) )
|
||||
|
||||
(** Usage: [fail lexbuf env token_list last_input_needed]
|
||||
|
||||
Raises an error with meaningful hints about what the parsing error was. [lexbuf] is the lexing
|
||||
buffer state at the failure point, [env] is the Menhir environment and [last_input_needed] is
|
||||
the last checkpoint of a valid Menhir state before the parsing error. [token_list] is provided
|
||||
by things like {!val: Surface.Lexer.token_list_language_agnostic} and is used to provide
|
||||
suggestions of the tokens acceptable at the failure point *)
|
||||
let fail (lexbuf : lexbuf) (env : 'semantic_value I.env) (token_list : (string * Parser.token) list)
|
||||
(last_input_needed : 'semantic_value I.env option) : 'a =
|
||||
let wrong_token = Utf8.lexeme lexbuf in
|
||||
@ -94,7 +118,6 @@ let fail (lexbuf : lexbuf) (env : 'semantic_value I.env) (token_list : (string *
|
||||
if levx = levy then String.length x - String.length y else levx - levy)
|
||||
acceptable_tokens
|
||||
in
|
||||
|
||||
let similar_token_msg =
|
||||
if List.length similar_acceptable_tokens = 0 then None
|
||||
else
|
||||
@ -122,10 +145,11 @@ let fail (lexbuf : lexbuf) (env : 'semantic_value I.env) (token_list : (string *
|
||||
in
|
||||
raise_parser_error (lexing_positions lexbuf) last_positions (Utf8.lexeme lexbuf) msg
|
||||
|
||||
(** Main parsing loop *)
|
||||
let rec loop (next_token : unit -> Parser.token * Lexing.position * Lexing.position)
|
||||
(token_list : (string * Parser.token) list) (lexbuf : lexbuf)
|
||||
(last_input_needed : 'semantic_value I.env option) (checkpoint : 'semantic_value I.checkpoint) :
|
||||
Catala_ast.source_file_or_master =
|
||||
Ast.source_file_or_master =
|
||||
match checkpoint with
|
||||
| I.InputNeeded env ->
|
||||
let token = next_token () in
|
||||
@ -140,62 +164,114 @@ let rec loop (next_token : unit -> Parser.token * Lexing.position * Lexing.posit
|
||||
(* Cannot happen as we stop at syntax error immediatly *)
|
||||
assert false
|
||||
|
||||
(** Stub that wraps the parsing main loop and handles the Menhir/Sedlex type difference for
|
||||
[lexbuf]. *)
|
||||
let sedlex_with_menhir (lexer' : lexbuf -> Parser.token) (token_list : (string * Parser.token) list)
|
||||
(target_rule : Lexing.position -> 'semantic_value I.checkpoint) (lexbuf : lexbuf) :
|
||||
Catala_ast.source_file_or_master =
|
||||
Ast.source_file_or_master =
|
||||
let lexer : unit -> Parser.token * Lexing.position * Lexing.position =
|
||||
with_tokenizer lexer' lexbuf
|
||||
in
|
||||
try loop lexer token_list lexbuf None (target_rule (fst @@ Sedlexing.lexing_positions lexbuf))
|
||||
with Sedlexing.MalFormed | Sedlexing.InvalidCodepoint _ ->
|
||||
Lexer.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf) "malformed token"
|
||||
Lexer.raise_lexer_error (lexing_positions lexbuf) (Utf8.lexeme lexbuf)
|
||||
|
||||
let rec parse_source_files (source_files : string list) (language : Cli.frontend_lang) :
|
||||
Catala_ast.program =
|
||||
match source_files with
|
||||
| [] -> { program_items = []; program_source_files = [] }
|
||||
| source_file :: rest -> (
|
||||
Cli.debug_print (Printf.sprintf "Parsing %s" source_file);
|
||||
let input = try open_in source_file with Sys_error msg -> Errors.raise_error msg in
|
||||
let lexbuf = Sedlexing.Utf8.from_channel input in
|
||||
Sedlexing.set_filename lexbuf source_file;
|
||||
Parse_utils.current_file := source_file;
|
||||
let lexer_lang =
|
||||
match language with
|
||||
| `Fr -> Lexer_fr.lexer_fr
|
||||
| `En -> Lexer_en.lexer_en
|
||||
| `NonVerbose -> Lexer.lexer
|
||||
(** {1 API} *)
|
||||
|
||||
(** Parses a single source file *)
|
||||
let rec parse_source_file (source_file : Pos.input_file) (language : Cli.frontend_lang) :
|
||||
Ast.program =
|
||||
Cli.debug_print
|
||||
(Printf.sprintf "Parsing %s" (match source_file with FileName s | Contents s -> s));
|
||||
let lexbuf, input =
|
||||
match source_file with
|
||||
| FileName source_file -> (
|
||||
try
|
||||
let input = open_in source_file in
|
||||
(Sedlexing.Utf8.from_channel input, Some input)
|
||||
with Sys_error msg -> Errors.raise_error msg )
|
||||
| Contents contents -> (Sedlexing.Utf8.from_gen (Gen.of_string contents), None)
|
||||
in
|
||||
let source_file_name = match source_file with FileName s -> s | Contents _ -> "stdin" in
|
||||
Sedlexing.set_filename lexbuf source_file_name;
|
||||
Parse_utils.current_file := source_file_name;
|
||||
let lexer_lang =
|
||||
match language with
|
||||
| `Fr -> Lexer_fr.lexer_fr
|
||||
| `En -> Lexer_en.lexer_en
|
||||
| `NonVerbose -> Lexer.lexer
|
||||
in
|
||||
let token_list_lang =
|
||||
match language with
|
||||
| `Fr -> Lexer_fr.token_list_fr
|
||||
| `En -> Lexer_en.token_list_en
|
||||
| `NonVerbose -> Lexer.token_list
|
||||
in
|
||||
let commands_or_includes =
|
||||
sedlex_with_menhir lexer_lang token_list_lang Parser.Incremental.source_file_or_master lexbuf
|
||||
in
|
||||
(match input with Some input -> close_in input | None -> ());
|
||||
match commands_or_includes with
|
||||
| Ast.SourceFile commands ->
|
||||
let program = expand_includes source_file_name commands language in
|
||||
{
|
||||
program_items = program.Ast.program_items;
|
||||
program_source_files = source_file_name :: program.Ast.program_source_files;
|
||||
}
|
||||
| Ast.MasterFile includes ->
|
||||
let current_source_file_dirname = Filename.dirname source_file_name in
|
||||
let includes =
|
||||
List.map
|
||||
(fun includ ->
|
||||
(if current_source_file_dirname = "./" then "" else current_source_file_dirname ^ "/")
|
||||
^ Pos.unmark includ)
|
||||
includes
|
||||
in
|
||||
let token_list_lang =
|
||||
match language with
|
||||
| `Fr -> Lexer_fr.token_list_fr
|
||||
| `En -> Lexer_en.token_list_en
|
||||
| `NonVerbose -> Lexer.token_list
|
||||
let new_program =
|
||||
List.fold_left
|
||||
(fun acc includ_file ->
|
||||
let includ_program = parse_source_file (FileName includ_file) language in
|
||||
{
|
||||
Ast.program_source_files =
|
||||
acc.Ast.program_source_files @ includ_program.program_source_files;
|
||||
Ast.program_items = acc.Ast.program_items @ includ_program.program_items;
|
||||
})
|
||||
{ Ast.program_source_files = []; Ast.program_items = [] }
|
||||
includes
|
||||
in
|
||||
let commands_or_includes =
|
||||
sedlex_with_menhir lexer_lang token_list_lang Parser.Incremental.source_file_or_master
|
||||
lexbuf
|
||||
in
|
||||
close_in input;
|
||||
match commands_or_includes with
|
||||
| Catala_ast.SourceFile commands ->
|
||||
let rest_program = parse_source_files rest language in
|
||||
{
|
||||
new_program with
|
||||
program_source_files = source_file_name :: new_program.program_source_files;
|
||||
}
|
||||
|
||||
(** Expands the include directives in a parsing result, thus parsing new source files *)
|
||||
and expand_includes (source_file : string) (commands : Ast.program_item list)
|
||||
(language : Cli.frontend_lang) : Ast.program =
|
||||
List.fold_left
|
||||
(fun acc command ->
|
||||
match command with
|
||||
| Ast.LawStructure (LawInclude (Ast.CatalaFile sub_source)) ->
|
||||
let source_dir = Filename.dirname source_file in
|
||||
let sub_source = Filename.concat source_dir (Pos.unmark sub_source) in
|
||||
let includ_program = parse_source_file (FileName sub_source) language in
|
||||
{
|
||||
program_items = commands @ rest_program.Catala_ast.program_items;
|
||||
program_source_files = source_file :: rest_program.Catala_ast.program_source_files;
|
||||
Ast.program_source_files =
|
||||
acc.Ast.program_source_files @ includ_program.program_source_files;
|
||||
Ast.program_items = acc.Ast.program_items @ includ_program.program_items;
|
||||
}
|
||||
| Catala_ast.MasterFile includes ->
|
||||
let current_source_file_dirname = Filename.dirname source_file in
|
||||
let includes =
|
||||
List.map
|
||||
(fun includ ->
|
||||
( if current_source_file_dirname = "./" then ""
|
||||
else current_source_file_dirname ^ "/" )
|
||||
^ Pos.unmark includ)
|
||||
includes
|
||||
| Ast.LawStructure (Ast.LawHeading (heading, commands')) ->
|
||||
let { Ast.program_items = commands'; Ast.program_source_files = new_sources } =
|
||||
expand_includes source_file (List.map (fun x -> Ast.LawStructure x) commands') language
|
||||
in
|
||||
let new_program = parse_source_files (includes @ rest) language in
|
||||
{
|
||||
new_program with
|
||||
program_source_files = source_file :: new_program.program_source_files;
|
||||
} )
|
||||
Ast.program_source_files = acc.Ast.program_source_files @ new_sources;
|
||||
Ast.program_items =
|
||||
acc.Ast.program_items
|
||||
@ [
|
||||
Ast.LawStructure
|
||||
(Ast.LawHeading (heading, List.map (fun (Ast.LawStructure x) -> x) commands'));
|
||||
];
|
||||
}
|
||||
| i -> { acc with Ast.program_items = acc.Ast.program_items @ [ i ] })
|
||||
{ Ast.program_source_files = []; Ast.program_items = [] }
|
||||
commands
|
||||
|
@ -8,155 +8,157 @@ let message s =
|
||||
| 1 ->
|
||||
"expected an inclusion of a Catala file, since this file is a master file which can only \
|
||||
contain inclusions of other Catala files\n"
|
||||
| 5 ->
|
||||
| 3 -> "expected some text or includes only\n"
|
||||
| 7 ->
|
||||
"expected another inclusion of a Catala file, since this file is a master file which can \
|
||||
only contain inclusions of other Catala files\n"
|
||||
| 277 -> "expected some text, another heading or a law article\n"
|
||||
| 282 -> "expected a code block, a metadata block, more law text or a heading\n"
|
||||
| 289 -> "expected a code block, a metadata block, more law text or a heading\n"
|
||||
| 285 -> "expected a declaration or a scope use\n"
|
||||
| 16 -> "expected the name of the scope you want to use\n"
|
||||
| 18 -> "expected a scope use precondition or a colon\n"
|
||||
| 19 -> "expected an expression which will act as the condition\n"
|
||||
| 20 -> "expected the first component of the date literal\n"
|
||||
| 22 -> "expected a \"/\"\n"
|
||||
| 23 -> "expected the second component of the date literal\n"
|
||||
| 24 -> "expected a \"/\"\n"
|
||||
| 25 -> "expected the third component of the date literal\n"
|
||||
| 26 -> "expected a delimiter to finish the date literal\n"
|
||||
| 48 -> "expected an operator to compose the expression on the left with\n"
|
||||
| 54 -> "expected an enum constructor to test if the expression on the left\n"
|
||||
| 53 -> "expected an operator to compose the expression on the left with\n"
|
||||
| 84 -> "expected an expression on the right side of the sum or minus operator\n"
|
||||
| 108 -> "expected an expression on the right side of the logical operator\n"
|
||||
| 56 -> "expected an expression for the argument of this function call\n"
|
||||
| 80 -> "expected an expression on the right side of the comparison operator\n"
|
||||
| 89 -> "expected an expression on the right side of the multiplication or division operator\n"
|
||||
| 86 -> "expected an operator to compose the expression on the left\n"
|
||||
| 135 -> "expected an expression standing for the set you want to test for membership\n"
|
||||
| 49 -> "expected an identifier standing for a struct field or a subscope name\n"
|
||||
| 159 -> "expected a colon after the scope use precondition\n"
|
||||
| 51 -> "expected a constructor, to get the payload of this enum case\n"
|
||||
| 92 -> "expected the \"for\" keyword to spell the aggregation\n"
|
||||
| 93 -> "expected an identifier for the aggregation bound variable\n"
|
||||
| 94 -> "expected the \"in\" keyword\n"
|
||||
| 95 ->
|
||||
"expected an expression standing for the set over which to compute the aggregation operation\n"
|
||||
| 97 -> "expected the \"for\" keyword and the expression to compute the aggregate\n"
|
||||
| 98 -> "expected an expression to compute its aggregation over the set\n"
|
||||
| 102 -> "expected an expression to take the negation of\n"
|
||||
| 45 -> "expected an expression to take the opposite of\n"
|
||||
| 34 -> "expected an expression to match with\n"
|
||||
| 143 -> "expected a pattern matching case\n"
|
||||
| 144 -> "expected the name of the constructor for the enum case in the pattern matching\n"
|
||||
| 150 ->
|
||||
| 8 -> "expected some text, another heading or a law article\n"
|
||||
| 350 -> "expected a heading, an article title or some text\n"
|
||||
| 335 -> "expected an article title, another heading or some text\n"
|
||||
| 340 -> "expected a code block, a metadata block, more law text or a heading\n"
|
||||
| 346 -> "expected a code block, a metadata block, more law text or a heading\n"
|
||||
| 341 -> "expected a declaration or a scope use\n"
|
||||
| 22 -> "expected the name of the scope you want to use\n"
|
||||
| 24 -> "expected a scope use precondition or a colon\n"
|
||||
| 25 -> "expected an expression which will act as the condition\n"
|
||||
| 26 -> "expected the first component of the date literal\n"
|
||||
| 28 -> "expected a \"/\"\n"
|
||||
| 29 -> "expected the second component of the date literal\n"
|
||||
| 30 -> "expected a \"/\"\n"
|
||||
| 31 -> "expected the third component of the date literal\n"
|
||||
| 32 -> "expected a delimiter to finish the date literal\n"
|
||||
| 71 -> "expected an operator to compose the expression on the left with\n"
|
||||
| 77 -> "expected an enum constructor to test if the expression on the left\n"
|
||||
| 76 -> "expected an operator to compose the expression on the left with\n"
|
||||
| 132 -> "expected an expression on the right side of the sum or minus operator\n"
|
||||
| 160 -> "expected an expression on the right side of the logical operator\n"
|
||||
| 79 -> "expected an expression for the argument of this function call\n"
|
||||
| 120 -> "expected an expression on the right side of the comparison operator\n"
|
||||
| 141 -> "expected an expression on the right side of the multiplication or division operator\n"
|
||||
| 134 -> "expected an operator to compose the expression on the left\n"
|
||||
| 170 -> "expected an expression standing for the set you want to test for membership\n"
|
||||
| 72 -> "expected an identifier standing for a struct field or a subscope name\n"
|
||||
| 218 -> "expected a colon after the scope use precondition\n"
|
||||
| 74 -> "expected a constructor, to get the payload of this enum case\n"
|
||||
| 35 -> "expected the \"for\" keyword to spell the aggregation\n"
|
||||
| 154 -> "expected an expression to take the negation of\n"
|
||||
| 68 -> "expected an expression to take the opposite of\n"
|
||||
| 55 -> "expected an expression to match with\n"
|
||||
| 202 -> "expected a pattern matching case\n"
|
||||
| 203 -> "expected the name of the constructor for the enum case in the pattern matching\n"
|
||||
| 209 ->
|
||||
"expected a binding for the constructor payload, or a colon and the matching case expression\n"
|
||||
| 151 -> "expected an identifier for this enum case binding\n"
|
||||
| 147 -> "expected a colon and then the expression for this matching case\n"
|
||||
| 153 -> "expected a colon or a binding for the enum constructor payload\n"
|
||||
| 148 -> "expected an expression for this pattern matching case\n"
|
||||
| 145 ->
|
||||
| 210 -> "expected an identifier for this enum case binding\n"
|
||||
| 206 -> "expected a colon and then the expression for this matching case\n"
|
||||
| 212 -> "expected a colon or a binding for the enum constructor payload\n"
|
||||
| 207 -> "expected an expression for this pattern matching case\n"
|
||||
| 204 ->
|
||||
"expected another match case or the rest of the expression since the previous match case is \
|
||||
complete\n"
|
||||
| 142 -> "expected the \"with patter\" keyword to complete the pattern matching expression\n"
|
||||
| 35 -> "expected an expression inside the parenthesis\n"
|
||||
| 128 -> "unmatched parenthesis that should have been closed by here\n"
|
||||
| 57 -> "expected a unit for this literal, or a valid operator to complete the expression \n"
|
||||
| 37 -> "expected an expression for the test of the conditional\n"
|
||||
| 138 -> "expected an expression the for the \"then\" branch of the conditiona\n"
|
||||
| 139 ->
|
||||
| 201 -> "expected the \"with patter\" keyword to complete the pattern matching expression\n"
|
||||
| 57 -> "expected an expression inside the parenthesis\n"
|
||||
| 193 -> "unmatched parenthesis that should have been closed by here\n"
|
||||
| 80 -> "expected a unit for this literal, or a valid operator to complete the expression \n"
|
||||
| 60 -> "expected an expression for the test of the conditional\n"
|
||||
| 189 -> "expected an expression the for the \"then\" branch of the conditiona\n"
|
||||
| 190 ->
|
||||
"expected the \"else\" branch of this conditional expression as the \"then\" branch is \
|
||||
complete\n"
|
||||
| 140 -> "expected an expression for the \"else\" branch of this conditional construction\n"
|
||||
| 137 -> "expected the \"then\" keyword as the conditional expression is complete\n"
|
||||
| 39 ->
|
||||
| 191 -> "expected an expression for the \"else\" branch of this conditional construction\n"
|
||||
| 188 -> "expected the \"then\" keyword as the conditional expression is complete\n"
|
||||
| 62 ->
|
||||
"expected the \"all\" keyword to mean the \"for all\" construction of the universal test\n"
|
||||
| 114 -> "expected an identifier for the bound variable of the universal test\n"
|
||||
| 115 -> "expected the \"in\" keyword for the rest of the universal test\n"
|
||||
| 116 -> "expected the expression designating the set on which to perform the universal test\n"
|
||||
| 117 -> "expected the \"we have\" keyword for this universal test\n"
|
||||
| 113 -> "expected an expression for the universal test\n"
|
||||
| 122 -> "expected an identifier that will designate the existential witness for the test\n"
|
||||
| 123 -> "expected the \"in\" keyword to continue this existential test\n"
|
||||
| 124 -> "expected an expression that designates the set subject to the existential test\n"
|
||||
| 125 -> "expected a keyword to form the \"such that\" expression for the existential test\n"
|
||||
| 126 -> "expected a keyword to complete the \"such that\" construction\n"
|
||||
| 120 -> "expected an expression for the existential test\n"
|
||||
| 64 ->
|
||||
| 174 -> "expected an identifier for the bound variable of the universal test\n"
|
||||
| 175 -> "expected the \"in\" keyword for the rest of the universal test\n"
|
||||
| 176 -> "expected the expression designating the set on which to perform the universal test\n"
|
||||
| 177 -> "expected the \"we have\" keyword for this universal test\n"
|
||||
| 173 -> "expected an expression for the universal test\n"
|
||||
| 182 -> "expected an identifier that will designate the existential witness for the test\n"
|
||||
| 183 -> "expected the \"in\" keyword to continue this existential test\n"
|
||||
| 184 -> "expected an expression that designates the set subject to the existential test\n"
|
||||
| 185 -> "expected a keyword to form the \"such that\" expression for the existential test\n"
|
||||
| 186 -> "expected a keyword to complete the \"such that\" construction\n"
|
||||
| 180 -> "expected an expression for the existential test\n"
|
||||
| 89 ->
|
||||
"expected a payload for the enum case constructor, or the rest of the expression (with an \
|
||||
operator ?)\n"
|
||||
| 65 -> "expected an expression for the content of this enum case\n"
|
||||
| 130 ->
|
||||
| 90 -> "expected structure fields introduced by --\n"
|
||||
| 91 -> "expected the name of the structure field\n"
|
||||
| 95 -> "expected a colon\n"
|
||||
| 96 -> "expected the expression for this struct field\n"
|
||||
| 92 -> "expected another structure field or the closing bracket\n"
|
||||
| 93 -> "expected the name of the structure field\n"
|
||||
| 164 -> "expected an expression for the content of this enum case\n"
|
||||
| 165 ->
|
||||
"the expression for the content of the enum case is already well-formed, expected an \
|
||||
operator to form a bigger expression\n"
|
||||
| 66 -> "expected a struct field creation introduced by a dash\n"
|
||||
| 67 -> "expected the name of field of the struct that you are building\n"
|
||||
| 71 -> "expected a colon and then the expression for the field of the struct\n"
|
||||
| 72 -> "expected an expression for the field of the struct\n"
|
||||
| 68 -> "expected another field of the struct or the end of the struct literal\n"
|
||||
| 69 -> "expected another field of the struct\n"
|
||||
| 44 -> "expected the keyword following cardinal to compute the number of elements in a set\n"
|
||||
| 160 -> "expected a scope use item: a rule, definition or assertion\n"
|
||||
| 161 -> "expected the name of the variable subject to the rule\n"
|
||||
| 180 ->
|
||||
| 67 -> "expected the keyword following cardinal to compute the number of elements in a set\n"
|
||||
| 219 -> "expected a scope use item: a rule, definition or assertion\n"
|
||||
| 254 -> "expected the name of the variable subject to the rule\n"
|
||||
| 232 ->
|
||||
"expected a condition or a consequence for this rule, or the rest of the variable qualified \
|
||||
name\n"
|
||||
| 175 -> "expected a condition or a consequence for this rule\n"
|
||||
| 166 -> "expected filled or not filled for a rule consequence\n"
|
||||
| 176 -> "expected the name of the parameter for this dependent variable \n"
|
||||
| 163 -> "expected the expression of the rule\n"
|
||||
| 169 -> "expected the filled keyword the this rule \n"
|
||||
| 181 -> "expected a struct field or a sub-scope context item after the dot\n"
|
||||
| 183 -> "expected the name of the variable you want to define\n"
|
||||
| 184 -> "expected the defined as keyword to introduce the definition of this variable\n"
|
||||
| 186 -> "expected an expression for the consequence of this definition under condition\n"
|
||||
| 185 ->
|
||||
| 261 -> "expected a condition or a consequence for this rule\n"
|
||||
| 256 -> "expected filled or not filled for a rule consequence\n"
|
||||
| 262 -> "expected the name of the parameter for this dependent variable \n"
|
||||
| 255 -> "expected the expression of the rule\n"
|
||||
| 259 -> "expected the filled keyword the this rule \n"
|
||||
| 233 -> "expected a struct field or a sub-scope context item after the dot\n"
|
||||
| 220 -> "expected the name of the label\n"
|
||||
| 250 -> "expected a rule or a definition after the label declaration\n"
|
||||
| 251 -> "expected the label to which the exception is referring back\n"
|
||||
| 253 -> "expected a rule or a definition after the exception declaration\n"
|
||||
| 266 -> "expected the name of the variable you want to define\n"
|
||||
| 267 -> "expected the defined as keyword to introduce the definition of this variable\n"
|
||||
| 269 -> "expected an expression for the consequence of this definition under condition\n"
|
||||
| 268 ->
|
||||
"expected a expression for defining this function, introduced by the defined as keyword\n"
|
||||
| 187 -> "expected an expression for the definition\n"
|
||||
| 190 -> "expected an expression that shoud be asserted during execution\n"
|
||||
| 191 -> "expecting the name of the varying variable\n"
|
||||
| 193 -> "the variable varies with an expression that was expected here\n"
|
||||
| 194 -> "expected an indication about the variation sense of the variable, or a new scope item\n"
|
||||
| 192 -> "expected an indication about what this variable varies with\n"
|
||||
| 164 -> "expected an expression for this condition\n"
|
||||
| 172 -> "expected a consequence for this definition under condition\n"
|
||||
| 203 -> "expected an expression for this definition under condition\n"
|
||||
| 199 -> "expected the name of the variable that should be fixed\n"
|
||||
| 200 -> "expected the legislative text by which the value of the variable is fixed\n"
|
||||
| 201 -> "expected the legislative text by which the value of the variable is fixed\n"
|
||||
| 207 -> "expected a new scope use item \n"
|
||||
| 210 -> "expected the kind of the declaration (struct, scope or enum)\n"
|
||||
| 211 -> "expected the struct name\n"
|
||||
| 212 -> "expected a colon\n"
|
||||
| 213 -> "expected struct data or condition\n"
|
||||
| 214 -> "expected the name of this struct data \n"
|
||||
| 215 -> "expected the type of this struct data, introduced by the content keyword\n"
|
||||
| 216 -> "expected the type of this struct data\n"
|
||||
| 240 -> "expected the name of this struct condition\n"
|
||||
| 233 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 234 -> "expected the type of the parameter of this struct data function\n"
|
||||
| 238 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 227 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 230 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 243 -> "expected the name of the scope you are declaring\n"
|
||||
| 244 -> "expected a colon followed by the list of context items of this scope\n"
|
||||
| 245 -> "expected a context item introduced by \"context\"\n"
|
||||
| 246 -> "expected the name of this new context item\n"
|
||||
| 247 -> "expected the kind of this context item: is it a condition, a sub-scope or a data?\n"
|
||||
| 248 -> "expected the name of the subscope for this context item\n"
|
||||
| 255 -> "expected the next context item, or another declaration or scope use\n"
|
||||
| 250 -> "expected the type of this context item\n"
|
||||
| 251 -> "expected the next context item or a dependency declaration for this item\n"
|
||||
| 253 -> "expected the next context item or a dependency declaration for this item\n"
|
||||
| 258 -> "expected the name of your enum\n"
|
||||
| 259 -> "expected a colon\n"
|
||||
| 260 -> "expected an enum case\n"
|
||||
| 261 -> "expected the name of an enum case \n"
|
||||
| 262 -> "expected a payload for your enum case, or another case or declaration \n"
|
||||
| 263 -> "expected a content type\n"
|
||||
| 268 -> "expected another enum case, or a new declaration or scope use\n"
|
||||
| 14 -> "expected a declaration or a scope use\n"
|
||||
| 15 -> "expected a declaration or a scope use\n"
|
||||
| 274 ->
|
||||
"should not happen, please file an issue at https://github.com/CatalaLang/catala/issues\n"
|
||||
| 270 -> "expected an expression for the definition\n"
|
||||
| 222 -> "expected an expression that shoud be asserted during execution\n"
|
||||
| 223 -> "expecting the name of the varying variable\n"
|
||||
| 226 -> "the variable varies with an expression that was expected here\n"
|
||||
| 227 -> "expected an indication about the variation sense of the variable, or a new scope item\n"
|
||||
| 225 -> "expected an indication about what this variable varies with\n"
|
||||
| 235 -> "expected an expression for this condition\n"
|
||||
| 245 -> "expected a consequence for this definition under condition\n"
|
||||
| 241 -> "expected an expression for this definition under condition\n"
|
||||
| 237 -> "expected the name of the variable that should be fixed\n"
|
||||
| 238 -> "expected the legislative text by which the value of the variable is fixed\n"
|
||||
| 239 -> "expected the legislative text by which the value of the variable is fixed\n"
|
||||
| 248 -> "expected a new scope use item \n"
|
||||
| 277 -> "expected the kind of the declaration (struct, scope or enum)\n"
|
||||
| 278 -> "expected the struct name\n"
|
||||
| 279 -> "expected a colon\n"
|
||||
| 280 -> "expected struct data or condition\n"
|
||||
| 281 -> "expected the name of this struct data \n"
|
||||
| 282 -> "expected the type of this struct data, introduced by the content keyword\n"
|
||||
| 283 -> "expected the type of this struct data\n"
|
||||
| 297 -> "expected the name of this struct condition\n"
|
||||
| 290 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 291 -> "expected the type of the parameter of this struct data function\n"
|
||||
| 295 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 287 -> "expected a new struct data, or another declaration or scope use\n"
|
||||
| 300 -> "expected the name of the scope you are declaring\n"
|
||||
| 301 -> "expected a colon followed by the list of context items of this scope\n"
|
||||
| 302 -> "expected a context item introduced by \"context\"\n"
|
||||
| 303 -> "expected the name of this new context item\n"
|
||||
| 304 -> "expected the kind of this context item: is it a condition, a sub-scope or a data?\n"
|
||||
| 305 -> "expected the name of the subscope for this context item\n"
|
||||
| 312 -> "expected another scope context item or the end of the scope declaration\n"
|
||||
| 307 -> "expected the type of this context item\n"
|
||||
| 308 -> "expected the next context item or a dependency declaration for this item\n"
|
||||
| 310 -> "expected the next context item or a dependency declaration for this item\n"
|
||||
| 315 -> "expected the name of your enum\n"
|
||||
| 316 -> "expected a colon\n"
|
||||
| 317 -> "expected an enum case\n"
|
||||
| 318 -> "expected the name of an enum case \n"
|
||||
| 319 -> "expected a payload for your enum case, or another case or declaration \n"
|
||||
| 320 -> "expected a content type\n"
|
||||
| 325 -> "expected another enum case, or a new declaration or scope use\n"
|
||||
| 18 -> "expected a declaration or a scope use\n"
|
||||
| 19 -> "expected some text or the beginning of a code section\n"
|
||||
| 20 -> "expected a declaration or a scope use\n"
|
||||
| 21 -> "should not happen\n"
|
||||
| 331 -> "expected a metadata-closing tag\n"
|
||||
| 332 -> "expected a metadata-closing tag\n"
|
||||
| _ -> raise Not_found
|
||||
|
@ -12,4 +12,15 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
let _ = Legifrance_catala.Main.main ()
|
||||
open Ast
|
||||
|
||||
let format_primitive_typ (fmt : Format.formatter) (t : primitive_typ) : unit =
|
||||
match t with
|
||||
| Integer -> Format.fprintf fmt "integer"
|
||||
| Decimal -> Format.fprintf fmt "decimal"
|
||||
| Boolean -> Format.fprintf fmt "boolean"
|
||||
| Money -> Format.fprintf fmt "money"
|
||||
| Duration -> Format.fprintf fmt "duration"
|
||||
| Text -> Format.fprintf fmt "text"
|
||||
| Date -> Format.fprintf fmt "date"
|
||||
| Named constructor -> Format.fprintf fmt "%s" constructor
|
77
src/catala/catala_surface/surface.mld
Normal file
@ -0,0 +1,77 @@
|
||||
{0 Catala surface representation }
|
||||
|
||||
This representation is the first in the compilation chain
|
||||
(see {{: index.html#architecture} Architecture}). Its purpose is to
|
||||
host the output of the Catala parser, before any transformations have been made.
|
||||
|
||||
The module describing the abstract syntax tree is:
|
||||
|
||||
{!modules: Surface.Ast}
|
||||
|
||||
This representation can also be weaved into literate programming outputs
|
||||
using the {{:literate.html} literate programming modules}.
|
||||
|
||||
{1 Lexing }
|
||||
|
||||
Relevant modules:
|
||||
|
||||
{!modules: Surface.Lexer Surface.Lexer_fr Surface.Lexer_en}
|
||||
|
||||
The lexing in the Catala compiler is done using
|
||||
{{: https://github.com/ocaml-community/sedlex} sedlex}, the modern OCaml lexer
|
||||
that offers full support for UTF-8. This support enables users of non-English
|
||||
languages to use their favorite diacritics and symbols in their code.
|
||||
|
||||
While the parser of Catala is unique, three different lexers can be used to
|
||||
produce the parser tokens.
|
||||
|
||||
{ul
|
||||
{li {!module: Surface.Lexer} corresponds to a concise and programming-language-like
|
||||
syntax for Catala. Examples of this syntax can be found in the test suite
|
||||
of the compiler.}
|
||||
{li {!module: Surface.Lexer_en} is the adaptation of {!module: Surface.Lexer}
|
||||
with verbose English keywords matching legal concepts.}
|
||||
{li {!module: Surface.Lexer_fr} is the adaptation of {!module: Surface.Lexer}
|
||||
with verbose French keywords matching legal concepts.}
|
||||
}
|
||||
|
||||
{1 Parsing }
|
||||
|
||||
Relevant modules:
|
||||
|
||||
{!modules: Surface.Parser Surface.Parser_driver Surface.Parser_errors}
|
||||
|
||||
The Catala compiler uses {{: http://cambium.inria.fr/~fpottier/menhir/} Menhir}
|
||||
to perform its parsing.
|
||||
|
||||
{!module: Surface.Parser} is the main file where the parser tokens and the
|
||||
grammar is declared. It is automatically translated into its parsing automata
|
||||
equivalent by Menhir.
|
||||
|
||||
In order to provide decent syntax error messages, the Catala compiler uses the
|
||||
novel error handling provided by Menhir and detailed in Section 11 of the
|
||||
{{: http://cambium.inria.fr/~fpottier/menhir/manual.pdf} Menhir manual}.
|
||||
|
||||
A [parser.messages] source file has been manually annotated with custom
|
||||
error message for every potential erroneous state of the parser, and Menhir
|
||||
automatically generated the {!module: Surface.Parser_errors} module containing
|
||||
the function linking the erroneous parser states to the custom error message.
|
||||
|
||||
To wrap it up, {!module: Surface.Parser_driver} glues all the parsing and
|
||||
lexing together to perform the translation from source code to abstract syntax
|
||||
tree, with meaningful error messages.
|
||||
|
||||
{1 Name resolution and translation }
|
||||
|
||||
Relevant modules:
|
||||
|
||||
{!modules: Surface.Name_resolution Surface.Desugaring}
|
||||
|
||||
The desugaring consists of translating {!module: Surface.Ast} to
|
||||
{!module: Desugared.Ast} of the {{: desugared.html} desugared representation}.
|
||||
The translation is implemented in
|
||||
{!module: Surface.Desugaring}, but it relies on a helper module to perform the
|
||||
name resolution: {!module: Surface.Name_resolution}. Indeed, in
|
||||
{!module: Surface.Ast}, the variables identifiers are just [string], whereas in
|
||||
{!module: Desugared.Ast} they have been turned into well-categorized types
|
||||
with an unique identifier like {!type: Scopelang.Ast.ScopeName.t}.
|
143
src/catala/default_calculus/ast.ml
Normal file
@ -0,0 +1,143 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Uid = Utils.Uid
|
||||
|
||||
(** Abstract syntax tree for the default calculus *)
|
||||
|
||||
(** {1 Abstract syntax tree} *)
|
||||
|
||||
type typ_lit = TBool | TUnit | TInt | TRat | TMoney | TDate | TDuration
|
||||
|
||||
type typ =
|
||||
| TLit of typ_lit
|
||||
| TTuple of typ Pos.marked list
|
||||
| TEnum of typ Pos.marked list
|
||||
| TArrow of typ Pos.marked * typ Pos.marked
|
||||
| TArray of typ Pos.marked
|
||||
| TAny
|
||||
|
||||
type date = ODate.Unix.t
|
||||
|
||||
type duration = Z.t
|
||||
|
||||
type lit =
|
||||
| LBool of bool
|
||||
| LEmptyError
|
||||
| LInt of Z.t
|
||||
| LRat of Q.t
|
||||
| LMoney of Z.t
|
||||
| LUnit
|
||||
| LDate of date
|
||||
| LDuration of duration
|
||||
|
||||
type op_kind =
|
||||
| KInt
|
||||
| KRat
|
||||
| KMoney
|
||||
| KDate
|
||||
| KDuration (** All ops don't have a Kdate and KDuration *)
|
||||
|
||||
type ternop = Fold
|
||||
|
||||
type binop =
|
||||
| And
|
||||
| Or
|
||||
| Add of op_kind
|
||||
| Sub of op_kind
|
||||
| Mult of op_kind
|
||||
| Div of op_kind
|
||||
| Lt of op_kind
|
||||
| Lte of op_kind
|
||||
| Gt of op_kind
|
||||
| Gte of op_kind
|
||||
| Eq
|
||||
| Neq
|
||||
| Map
|
||||
|
||||
type log_entry = VarDef | BeginCall | EndCall
|
||||
|
||||
type unop =
|
||||
| Not
|
||||
| Minus of op_kind
|
||||
| ErrorOnEmpty
|
||||
| Log of log_entry * Utils.Uid.MarkedString.info list
|
||||
| Length
|
||||
| IntToRat
|
||||
|
||||
type operator = Ternop of ternop | Binop of binop | Unop of unop
|
||||
|
||||
(** The expressions use the {{:https://lepigre.fr/ocaml-bindlib/} Bindlib} library, based on
|
||||
higher-order abstract syntax*)
|
||||
type expr =
|
||||
| EVar of expr Bindlib.var Pos.marked
|
||||
| ETuple of (expr Pos.marked * Uid.MarkedString.info option) list
|
||||
(** The [MarkedString.info] is the former struct field name*)
|
||||
| ETupleAccess of expr Pos.marked * int * Uid.MarkedString.info option
|
||||
(** The [MarkedString.info] is the former struct field name*)
|
||||
| EInj of expr Pos.marked * int * Uid.MarkedString.info * typ Pos.marked list
|
||||
(** The [MarkedString.info] is the former enum case name *)
|
||||
| EMatch of expr Pos.marked * (expr Pos.marked * Uid.MarkedString.info) list
|
||||
(** The [MarkedString.info] is the former enum case name *)
|
||||
| EArray of expr Pos.marked list
|
||||
| ELit of lit
|
||||
| EAbs of Pos.t * (expr, expr Pos.marked) Bindlib.mbinder * typ Pos.marked list
|
||||
| EApp of expr Pos.marked * expr Pos.marked list
|
||||
| EAssert of expr Pos.marked
|
||||
| EOp of operator
|
||||
| EDefault of expr Pos.marked list * expr Pos.marked * expr Pos.marked
|
||||
| EIfThenElse of expr Pos.marked * expr Pos.marked * expr Pos.marked
|
||||
|
||||
(** {1 Variable helpers} *)
|
||||
|
||||
module Var = struct
|
||||
type t = expr Bindlib.var
|
||||
|
||||
let make (s : string Pos.marked) : t =
|
||||
Bindlib.new_var
|
||||
(fun (x : expr Bindlib.var) : expr -> EVar (x, Pos.get_position s))
|
||||
(Pos.unmark s)
|
||||
|
||||
let compare x y = Bindlib.compare_vars x y
|
||||
end
|
||||
|
||||
module VarMap = Map.Make (Var)
|
||||
|
||||
type vars = expr Bindlib.mvar
|
||||
|
||||
let make_var ((x, pos) : Var.t Pos.marked) : expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply (fun x -> (x, pos)) (Bindlib.box_var x)
|
||||
|
||||
let make_abs (xs : vars) (e : expr Pos.marked Bindlib.box) (pos_binder : Pos.t)
|
||||
(taus : typ Pos.marked list) (pos : Pos.t) : expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply (fun b -> (EAbs (pos_binder, b, taus), pos)) (Bindlib.bind_mvar xs e)
|
||||
|
||||
let make_app (e : expr Pos.marked Bindlib.box) (u : expr Pos.marked Bindlib.box list) (pos : Pos.t)
|
||||
: expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply2 (fun e u -> (EApp (e, u), pos)) e (Bindlib.box_list u)
|
||||
|
||||
let make_let_in (x : Var.t) (tau : typ Pos.marked) (e1 : expr Pos.marked Bindlib.box)
|
||||
(e2 : expr Pos.marked Bindlib.box) : expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply2
|
||||
(fun e u -> (EApp (e, u), Pos.get_position (Bindlib.unbox e2)))
|
||||
(make_abs
|
||||
(Array.of_list [ x ])
|
||||
e2
|
||||
(Pos.get_position (Bindlib.unbox e2))
|
||||
[ tau ]
|
||||
(Pos.get_position (Bindlib.unbox e2)))
|
||||
(Bindlib.box_list [ e1 ])
|
||||
|
||||
type binder = (expr, expr Pos.marked) Bindlib.binder
|
40
src/catala/default_calculus/dcalc.mld
Normal file
@ -0,0 +1,40 @@
|
||||
{0 Default calculus}
|
||||
|
||||
This representation is the fourth in the compilation chain
|
||||
(see {{: index.html#architecture} Architecture}). Its main difference
|
||||
with the previous {{: desugared.html} desugared representation} is that scopes
|
||||
have been lowered into regular functions, and enums and structs have been
|
||||
lowered to sum and product types.
|
||||
|
||||
The module describing the abstract syntax tree is:
|
||||
|
||||
{!modules: Dcalc.Ast}
|
||||
|
||||
Printing helpers can be found in {!module: Dcalc.Print}.
|
||||
|
||||
This intermediate representation corresponds to the default calculus
|
||||
presented in the {{: https://github.com/CatalaLang/catala/raw/master/doc/formalization/formalization.pdf}
|
||||
Catala formalization}.
|
||||
|
||||
{1 Typing }
|
||||
|
||||
Related modules:
|
||||
|
||||
{!modules: Dcalc.Typing}
|
||||
|
||||
This representation is where the typing is performed. Indeed, {!module: Dcalc.Typing}
|
||||
implements the classical {{: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system} W algorithm}
|
||||
corresponding to a Hindley-Milner type system, without type constraints.
|
||||
|
||||
{1 Interpreter}
|
||||
|
||||
Related modules:
|
||||
|
||||
{!modules: Dcalc.Interpreter}
|
||||
|
||||
Since this representation is currently the last of the compilation chain,
|
||||
an {!module: Dcalc.Interpreter} module is provided to match the execution
|
||||
semantics of the default calculus.
|
||||
|
||||
Later, translations to a regular lambda calculus and/or a simple imperative
|
||||
language are bound to be added.
|
7
src/catala/default_calculus/dune
Normal file
@ -0,0 +1,7 @@
|
||||
(library
|
||||
(name dcalc)
|
||||
(public_name catala.dcalc)
|
||||
(libraries bindlib unionFind utils zarith zarith_stubs_js odate))
|
||||
|
||||
(documentation
|
||||
(package catala))
|
378
src/catala/default_calculus/interpreter.ml
Normal file
@ -0,0 +1,378 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Reference interpreter for the default calculus *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module Cli = Utils.Cli
|
||||
module A = Ast
|
||||
|
||||
(** {1 Helpers} *)
|
||||
|
||||
let is_empty_error (e : A.expr Pos.marked) : bool =
|
||||
match Pos.unmark e with ELit LEmptyError -> true | _ -> false
|
||||
|
||||
let empty_thunked_term : Ast.expr Pos.marked =
|
||||
let silent = Ast.Var.make ("_", Pos.no_pos) in
|
||||
Bindlib.unbox
|
||||
(Ast.make_abs
|
||||
(Array.of_list [ silent ])
|
||||
(Bindlib.box (Ast.ELit Ast.LEmptyError, Pos.no_pos))
|
||||
Pos.no_pos
|
||||
[ (Ast.TLit Ast.TUnit, Pos.no_pos) ]
|
||||
Pos.no_pos)
|
||||
|
||||
let rec type_eq (t1 : A.typ Pos.marked) (t2 : A.typ Pos.marked) : bool =
|
||||
match (Pos.unmark t1, Pos.unmark t2) with
|
||||
| A.TLit tl1, A.TLit tl2 -> tl1 = tl2
|
||||
| A.TTuple ts1, A.TTuple ts2 | A.TEnum ts1, A.TEnum ts2 -> (
|
||||
try List.for_all2 type_eq ts1 ts2 with Invalid_argument _ -> false )
|
||||
| A.TArray t1, A.TArray t2 -> type_eq t1 t2
|
||||
| A.TArrow (t11, t12), A.TArrow (t21, t22) -> type_eq t11 t12 && type_eq t21 t22
|
||||
| _, _ -> false
|
||||
|
||||
(** {1 Evaluation} *)
|
||||
|
||||
let rec evaluate_operator (op : A.operator Pos.marked) (args : A.expr Pos.marked list) :
|
||||
A.expr Pos.marked =
|
||||
Pos.same_pos_as
|
||||
( match (Pos.unmark op, List.map Pos.unmark args) with
|
||||
| A.Ternop A.Fold, [ _f; _init; EArray es ] ->
|
||||
Pos.unmark
|
||||
(List.fold_left
|
||||
(fun acc e' ->
|
||||
evaluate_expr (Pos.same_pos_as (A.EApp (List.nth args 0, [ acc; e' ])) e'))
|
||||
(List.nth args 1) es)
|
||||
| A.Binop A.And, [ ELit (LBool b1); ELit (LBool b2) ] -> A.ELit (LBool (b1 && b2))
|
||||
| A.Binop A.Or, [ ELit (LBool b1); ELit (LBool b2) ] -> A.ELit (LBool (b1 || b2))
|
||||
| A.Binop (A.Add KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LInt (Z.add i1 i2))
|
||||
| A.Binop (A.Sub KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LInt (Z.sub i1 i2))
|
||||
| A.Binop (A.Mult KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LInt (Z.mul i1 i2))
|
||||
| A.Binop (A.Div KInt), [ ELit (LInt i1); ELit (LInt i2) ] ->
|
||||
if i2 <> Z.zero then A.ELit (LInt (Z.div i1 i2))
|
||||
else
|
||||
Errors.raise_multispanned_error "division by zero at runtime"
|
||||
[
|
||||
(Some "The division operator:", Pos.get_position op);
|
||||
(Some "The null denominator:", Pos.get_position (List.nth args 2));
|
||||
]
|
||||
| A.Binop (A.Add KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LRat (Q.add i1 i2))
|
||||
| A.Binop (A.Sub KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LRat (Q.sub i1 i2))
|
||||
| A.Binop (A.Mult KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LRat (Q.mul i1 i2))
|
||||
| A.Binop (A.Div KRat), [ ELit (LRat i1); ELit (LRat i2) ] ->
|
||||
if i2 <> Q.zero then A.ELit (LRat (Q.div i1 i2))
|
||||
else
|
||||
Errors.raise_multispanned_error "division by zero at runtime"
|
||||
[
|
||||
(Some "The division operator:", Pos.get_position op);
|
||||
(Some "The null denominator:", Pos.get_position (List.nth args 2));
|
||||
]
|
||||
| A.Binop (A.Add KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] ->
|
||||
A.ELit (LMoney (Z.add i1 i2))
|
||||
| A.Binop (A.Sub KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] ->
|
||||
A.ELit (LMoney (Z.sub i1 i2))
|
||||
| A.Binop (A.Mult KMoney), [ ELit (LMoney i1); ELit (LRat i2) ] ->
|
||||
let rat_result = Q.mul (Q.of_bigint i1) i2 in
|
||||
let res, remainder = Z.div_rem (Q.num rat_result) (Q.den rat_result) in
|
||||
(* we perform nearest rounding when multiplying an amount of money by a decimal !*)
|
||||
let out =
|
||||
if Z.(of_int 2 * remainder >= Q.den rat_result) then Z.add res (Z.of_int 1) else res
|
||||
in
|
||||
A.ELit (LMoney out)
|
||||
| A.Binop (A.Div KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] ->
|
||||
if i2 <> Z.zero then A.ELit (LRat (Q.div (Q.of_bigint i1) (Q.of_bigint i2)))
|
||||
else
|
||||
Errors.raise_multispanned_error "division by zero at runtime"
|
||||
[
|
||||
(Some "The division operator:", Pos.get_position op);
|
||||
(Some "The null denominator:", Pos.get_position (List.nth args 2));
|
||||
]
|
||||
| A.Binop (A.Add KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LDuration (Z.( + ) i1 i2))
|
||||
| A.Binop (A.Sub KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LDuration (Z.( - ) i1 i2))
|
||||
| A.Binop (A.Sub KDate), [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LDuration (Z.of_int (ODuration.To.day (ODate.Unix.between i2 i1))))
|
||||
| A.Binop (A.Add KDate), [ ELit (LDate i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LDate (ODate.Unix.advance_by_days i1 (Z.to_int i2)))
|
||||
| A.Binop (A.Lt KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LBool (i1 < i2))
|
||||
| A.Binop (A.Lte KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LBool (i1 <= i2))
|
||||
| A.Binop (A.Gt KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LBool (i1 > i2))
|
||||
| A.Binop (A.Gte KInt), [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LBool (i1 >= i2))
|
||||
| A.Binop (A.Lt KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LBool Q.(i1 < i2))
|
||||
| A.Binop (A.Lte KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LBool Q.(i1 <= i2))
|
||||
| A.Binop (A.Gt KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LBool Q.(i1 > i2))
|
||||
| A.Binop (A.Gte KRat), [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LBool Q.(i1 >= i2))
|
||||
| A.Binop (A.Lt KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] -> A.ELit (LBool (i1 < i2))
|
||||
| A.Binop (A.Lte KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] -> A.ELit (LBool (i1 <= i2))
|
||||
| A.Binop (A.Gt KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] -> A.ELit (LBool (i1 > i2))
|
||||
| A.Binop (A.Gte KMoney), [ ELit (LMoney i1); ELit (LMoney i2) ] -> A.ELit (LBool (i1 >= i2))
|
||||
| A.Binop (A.Lt KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LBool (i1 < i2))
|
||||
| A.Binop (A.Lte KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LBool (i1 <= i2))
|
||||
| A.Binop (A.Gt KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LBool (i1 > i2))
|
||||
| A.Binop (A.Gte KDuration), [ ELit (LDuration i1); ELit (LDuration i2) ] ->
|
||||
A.ELit (LBool (i1 >= i2))
|
||||
| A.Binop (A.Lt KDate), [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LBool (ODate.Unix.compare i1 i2 < 0))
|
||||
| A.Binop (A.Lte KDate), [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LBool (ODate.Unix.compare i1 i2 <= 0))
|
||||
| A.Binop (A.Gt KDate), [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LBool (ODate.Unix.compare i1 i2 > 0))
|
||||
| A.Binop (A.Gte KDate), [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LBool (ODate.Unix.compare i1 i2 >= 0))
|
||||
| A.Binop A.Eq, [ ELit LUnit; ELit LUnit ] -> A.ELit (LBool true)
|
||||
| A.Binop A.Eq, [ ELit (LDuration i1); ELit (LDuration i2) ] -> A.ELit (LBool (i1 = i2))
|
||||
| A.Binop A.Eq, [ ELit (LDate i1); ELit (LDate i2) ] ->
|
||||
A.ELit (LBool (ODate.Unix.compare i1 i2 = 0))
|
||||
| A.Binop A.Eq, [ ELit (LMoney i1); ELit (LMoney i2) ] -> A.ELit (LBool (i1 = i2))
|
||||
| A.Binop A.Eq, [ ELit (LRat i1); ELit (LRat i2) ] -> A.ELit (LBool Q.(i1 = i2))
|
||||
| A.Binop A.Eq, [ ELit (LInt i1); ELit (LInt i2) ] -> A.ELit (LBool (i1 = i2))
|
||||
| A.Binop A.Eq, [ ELit (LBool b1); ELit (LBool b2) ] -> A.ELit (LBool (b1 = b2))
|
||||
| A.Binop A.Eq, [ EArray es1; EArray es2 ] ->
|
||||
A.ELit
|
||||
(LBool
|
||||
( try
|
||||
List.for_all2
|
||||
(fun e1 e2 ->
|
||||
match Pos.unmark (evaluate_operator op [ e1; e2 ]) with
|
||||
| A.ELit (LBool b) -> b
|
||||
| _ -> assert false
|
||||
(* should not happen *))
|
||||
es1 es2
|
||||
with Invalid_argument _ -> false ))
|
||||
| A.Binop A.Eq, [ ETuple es1; ETuple es2 ] ->
|
||||
A.ELit
|
||||
(LBool
|
||||
( try
|
||||
List.for_all2
|
||||
(fun (e1, _) (e2, _) ->
|
||||
match Pos.unmark (evaluate_operator op [ e1; e2 ]) with
|
||||
| A.ELit (LBool b) -> b
|
||||
| _ -> assert false
|
||||
(* should not happen *))
|
||||
es1 es2
|
||||
with Invalid_argument _ -> false ))
|
||||
| A.Binop A.Eq, [ EInj (e1, i1, _, ts1); EInj (e2, i2, _, ts2) ] ->
|
||||
A.ELit
|
||||
(LBool
|
||||
( try
|
||||
List.for_all2 type_eq ts1 ts2 && i1 = i2
|
||||
&&
|
||||
match Pos.unmark (evaluate_operator op [ e1; e2 ]) with
|
||||
| A.ELit (LBool b) -> b
|
||||
| _ -> assert false
|
||||
(* should not happen *)
|
||||
with Invalid_argument _ -> false ))
|
||||
| A.Binop A.Eq, [ _; _ ] -> A.ELit (LBool false) (* comparing anything else return false *)
|
||||
| A.Binop A.Neq, [ _; _ ] -> (
|
||||
match Pos.unmark (evaluate_operator (Pos.same_pos_as (A.Binop A.Eq) op) args) with
|
||||
| A.ELit (A.LBool b) -> A.ELit (A.LBool (not b))
|
||||
| _ -> assert false (*should not happen *) )
|
||||
| A.Binop A.Map, [ _; A.EArray es ] ->
|
||||
A.EArray
|
||||
(List.map
|
||||
(fun e' -> evaluate_expr (Pos.same_pos_as (A.EApp (List.nth args 0, [ e' ])) e'))
|
||||
es)
|
||||
| A.Binop _, ([ ELit LEmptyError; _ ] | [ _; ELit LEmptyError ]) -> A.ELit LEmptyError
|
||||
| A.Unop (A.Minus KInt), [ ELit (LInt i) ] -> A.ELit (LInt (Z.sub Z.zero i))
|
||||
| A.Unop (A.Minus KRat), [ ELit (LRat i) ] -> A.ELit (LRat (Q.sub Q.zero i))
|
||||
| A.Unop A.Not, [ ELit (LBool b) ] -> A.ELit (LBool (not b))
|
||||
| A.Unop A.Length, [ EArray es ] -> A.ELit (LInt (Z.of_int (List.length es)))
|
||||
| A.Unop A.IntToRat, [ ELit (LInt i) ] -> A.ELit (LRat (Q.of_bigint i))
|
||||
| A.Unop A.ErrorOnEmpty, [ e' ] ->
|
||||
if e' = A.ELit LEmptyError then
|
||||
Errors.raise_spanned_error
|
||||
"This variable evaluated to an empty term (no rule that defined it applied in this \
|
||||
situation)"
|
||||
(Pos.get_position op)
|
||||
else e'
|
||||
| A.Unop (A.Log (entry, infos)), [ e' ] ->
|
||||
if !Cli.trace_flag then
|
||||
match entry with
|
||||
| VarDef ->
|
||||
Cli.log_print
|
||||
(Format.asprintf "@[<hov 2>%a@ %a@ =@ %a@]" Print.format_log_entry entry
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ".")
|
||||
(fun fmt info -> Utils.Uid.MarkedString.format_info fmt info))
|
||||
infos Print.format_expr (e', Pos.no_pos))
|
||||
| _ ->
|
||||
Cli.log_print
|
||||
(Format.asprintf "@[<hov 2>%a@ %a@]" Print.format_log_entry entry
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ".")
|
||||
(fun fmt info -> Utils.Uid.MarkedString.format_info fmt info))
|
||||
infos)
|
||||
else ();
|
||||
e'
|
||||
| A.Unop _, [ ELit LEmptyError ] -> A.ELit LEmptyError
|
||||
| _ ->
|
||||
Errors.raise_multispanned_error
|
||||
"Operator applied to the wrong arguments\n(should nothappen if the term was well-typed)"
|
||||
( [ (Some "Operator:", Pos.get_position op) ]
|
||||
@ List.mapi
|
||||
(fun i arg ->
|
||||
( Some (Format.asprintf "Argument n°%d, value %a" (i + 1) Print.format_expr arg),
|
||||
Pos.get_position arg ))
|
||||
args ) )
|
||||
op
|
||||
|
||||
and evaluate_expr (e : A.expr Pos.marked) : A.expr Pos.marked =
|
||||
match Pos.unmark e with
|
||||
| EVar _ ->
|
||||
Errors.raise_spanned_error
|
||||
"free variable found at evaluation (should not happen if term was well-typed"
|
||||
(Pos.get_position e)
|
||||
| EApp (e1, args) -> (
|
||||
let e1 = evaluate_expr e1 in
|
||||
let args = List.map evaluate_expr args in
|
||||
match Pos.unmark e1 with
|
||||
| EAbs (_, binder, _) ->
|
||||
if Bindlib.mbinder_arity binder = List.length args then
|
||||
evaluate_expr (Bindlib.msubst binder (Array.of_list (List.map Pos.unmark args)))
|
||||
else
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "wrong function call, expected %d arguments, got %d"
|
||||
(Bindlib.mbinder_arity binder) (List.length args))
|
||||
(Pos.get_position e)
|
||||
| EOp op -> Pos.same_pos_as (Pos.unmark (evaluate_operator (Pos.same_pos_as op e1) args)) e
|
||||
| ELit LEmptyError -> Pos.same_pos_as (A.ELit LEmptyError) e
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"function has not been reduced to a lambda at evaluation (should not happen if the \
|
||||
term was well-typed"
|
||||
(Pos.get_position e) )
|
||||
| EAbs _ | ELit _ | EOp _ -> e (* thse are values *)
|
||||
| ETuple es -> Pos.same_pos_as (A.ETuple (List.map (fun (e', i) -> (evaluate_expr e', i)) es)) e
|
||||
| ETupleAccess (e1, n, _) -> (
|
||||
let e1 = evaluate_expr e1 in
|
||||
match Pos.unmark e1 with
|
||||
| ETuple es -> (
|
||||
match List.nth_opt es n with
|
||||
| Some (e', _) -> e'
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"the tuple has %d components but the %i-th element was requested (should not \
|
||||
happen if the term was well-type)"
|
||||
(List.length es) n)
|
||||
(Pos.get_position e1) )
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"the expression should be a tuple with %d components but is not (should not happen \
|
||||
if the term was well-typed)"
|
||||
n)
|
||||
(Pos.get_position e1) )
|
||||
| EInj (e1, n, i, ts) ->
|
||||
let e1' = evaluate_expr e1 in
|
||||
Pos.same_pos_as (A.EInj (e1', n, i, ts)) e
|
||||
| EMatch (e1, es) -> (
|
||||
let e1 = evaluate_expr e1 in
|
||||
match Pos.unmark e1 with
|
||||
| A.EInj (e1, n, _, _) ->
|
||||
let es_n, _ =
|
||||
match List.nth_opt es n with
|
||||
| Some es_n -> es_n
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
"sum type index error (should not happend if the term was well-typed)"
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let new_e = Pos.same_pos_as (A.EApp (es_n, [ e1 ])) e in
|
||||
evaluate_expr new_e
|
||||
| A.ELit A.LEmptyError -> Pos.same_pos_as (A.ELit A.LEmptyError) e
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"Expected a term having a sum type as an argument to a match (should not happend if \
|
||||
the term was well-typed"
|
||||
(Pos.get_position e1) )
|
||||
| EDefault (exceptions, just, cons) -> (
|
||||
let exceptions_orig = exceptions in
|
||||
let exceptions = List.map evaluate_expr exceptions in
|
||||
let empty_count = List.length (List.filter is_empty_error exceptions) in
|
||||
match List.length exceptions - empty_count with
|
||||
| 0 -> (
|
||||
let just = evaluate_expr just in
|
||||
match Pos.unmark just with
|
||||
| ELit LEmptyError -> Pos.same_pos_as (A.ELit LEmptyError) e
|
||||
| ELit (LBool true) -> evaluate_expr cons
|
||||
| ELit (LBool false) -> Pos.same_pos_as (A.ELit LEmptyError) e
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"Default justification has not been reduced to a boolean at evaluation (should not \
|
||||
happen if the term was well-typed"
|
||||
(Pos.get_position e) )
|
||||
| 1 -> List.find (fun sub -> not (is_empty_error sub)) exceptions
|
||||
| _ ->
|
||||
Errors.raise_multispanned_error
|
||||
"There is a conflict between multiple exceptions for assigning the same variable."
|
||||
(List.map
|
||||
(fun (_, except) -> (Some "This justification is true:", Pos.get_position except))
|
||||
(List.filter
|
||||
(fun (sub, _) -> not (is_empty_error sub))
|
||||
(List.map2 (fun x y -> (x, y)) exceptions exceptions_orig))) )
|
||||
| EIfThenElse (cond, et, ef) -> (
|
||||
match Pos.unmark (evaluate_expr cond) with
|
||||
| ELit (LBool true) -> evaluate_expr et
|
||||
| ELit (LBool false) -> evaluate_expr ef
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"Expected a boolean literal for the result of this condition (should not happen if the \
|
||||
term was well-typed)"
|
||||
(Pos.get_position cond) )
|
||||
| EArray es -> Pos.same_pos_as (A.EArray (List.map evaluate_expr es)) e
|
||||
| EAssert e' -> (
|
||||
match Pos.unmark (evaluate_expr e') with
|
||||
| ELit (LBool true) -> Pos.same_pos_as (Ast.ELit LUnit) e'
|
||||
| ELit (LBool false) -> (
|
||||
match Pos.unmark e' with
|
||||
| EApp ((Ast.EOp (Binop op), pos_op), [ ((ELit _, _) as e1); ((ELit _, _) as e2) ]) ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "Assertion failed: %a %a %a" Print.format_expr e1
|
||||
Print.format_binop (op, pos_op) Print.format_expr e2)
|
||||
(Pos.get_position e')
|
||||
| _ ->
|
||||
Errors.raise_spanned_error (Format.asprintf "Assertion failed") (Pos.get_position e')
|
||||
)
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"Expected a boolean literal for the result of this assertion (should not happen if the \
|
||||
term was well-typed)"
|
||||
(Pos.get_position e') )
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
(** Interpret a program. This function expects an expression typed as a function whose argument are
|
||||
all thunked. The function is executed by providing for each argument a thunked empty default. *)
|
||||
let interpret_program (e : Ast.expr Pos.marked) : (Ast.Var.t * Ast.expr Pos.marked) list =
|
||||
match Pos.unmark (evaluate_expr e) with
|
||||
| Ast.EAbs (_, binder, taus) -> (
|
||||
let application_term = List.map (fun _ -> empty_thunked_term) taus in
|
||||
let to_interpret = (Ast.EApp (e, application_term), Pos.no_pos) in
|
||||
match Pos.unmark (evaluate_expr to_interpret) with
|
||||
| Ast.ETuple args ->
|
||||
let vars, _ = Bindlib.unmbind binder in
|
||||
List.map2 (fun (arg, _) var -> (var, arg)) args (Array.to_list vars)
|
||||
| _ ->
|
||||
Errors.raise_spanned_error "The interpretation of a program should always yield a tuple"
|
||||
(Pos.get_position e) )
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
"The interpreter can only interpret terms starting with functions having thunked arguments"
|
||||
(Pos.get_position e)
|
228
src/catala/default_calculus/print.ml
Normal file
@ -0,0 +1,228 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
open Ast
|
||||
|
||||
let typ_needs_parens (e : typ Pos.marked) : bool =
|
||||
match Pos.unmark e with TArrow _ | TArray _ -> true | _ -> false
|
||||
|
||||
let format_tlit (fmt : Format.formatter) (l : typ_lit) : unit =
|
||||
match l with
|
||||
| TUnit -> Format.fprintf fmt "unit"
|
||||
| TBool -> Format.fprintf fmt "boolean"
|
||||
| TInt -> Format.fprintf fmt "integer"
|
||||
| TRat -> Format.fprintf fmt "decimal"
|
||||
| TMoney -> Format.fprintf fmt "money"
|
||||
| TDuration -> Format.fprintf fmt "duration"
|
||||
| TDate -> Format.fprintf fmt "date"
|
||||
|
||||
let rec format_typ (fmt : Format.formatter) (typ : typ Pos.marked) : unit =
|
||||
let format_typ_with_parens (fmt : Format.formatter) (t : typ Pos.marked) =
|
||||
if typ_needs_parens t then Format.fprintf fmt "(%a)" format_typ t
|
||||
else Format.fprintf fmt "%a" format_typ t
|
||||
in
|
||||
match Pos.unmark typ with
|
||||
| TLit l -> Format.fprintf fmt "%a" format_tlit l
|
||||
| TTuple ts ->
|
||||
Format.fprintf fmt "(%a)"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt " *@ ") format_typ)
|
||||
ts
|
||||
| TEnum ts ->
|
||||
Format.fprintf fmt "(%a)"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt " +@ ") format_typ)
|
||||
ts
|
||||
| TArrow (t1, t2) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a →@ %a@]" format_typ_with_parens t1 format_typ t2
|
||||
| TArray t1 -> Format.fprintf fmt "@[%a@ array@]" format_typ t1
|
||||
| TAny -> Format.fprintf fmt "any"
|
||||
|
||||
let format_lit (fmt : Format.formatter) (l : lit Pos.marked) : unit =
|
||||
match Pos.unmark l with
|
||||
| LBool b -> Format.fprintf fmt "%b" b
|
||||
| LInt i -> Format.fprintf fmt "%s" (Z.to_string i)
|
||||
| LEmptyError -> Format.fprintf fmt "∅"
|
||||
| LUnit -> Format.fprintf fmt "()"
|
||||
| LRat i ->
|
||||
let sign = Q.sign i in
|
||||
let n = Z.abs (Q.num i) in
|
||||
let d = Z.abs (Q.den i) in
|
||||
let int_part = Z.ediv n d in
|
||||
let n = ref (Z.erem n d) in
|
||||
let digits = ref [] in
|
||||
let leading_zeroes (digits : Z.t list) : int =
|
||||
match
|
||||
List.fold_right
|
||||
(fun digit num_leading_zeroes ->
|
||||
match num_leading_zeroes with
|
||||
| `End _ -> num_leading_zeroes
|
||||
| `Begin i -> if Z.(digit = zero) then `Begin (i + 1) else `End i)
|
||||
digits (`Begin 0)
|
||||
with
|
||||
| `End i -> i
|
||||
| `Begin i -> i
|
||||
in
|
||||
while
|
||||
!n <> Z.zero && List.length !digits - leading_zeroes !digits < !Utils.Cli.max_prec_digits
|
||||
do
|
||||
n := Z.mul !n (Z.of_int 10);
|
||||
digits := Z.ediv !n d :: !digits;
|
||||
n := Z.erem !n d
|
||||
done;
|
||||
Format.fprintf fmt "%s%a.%a%s"
|
||||
(if sign < 0 then "-" else "")
|
||||
Z.pp_print int_part
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun _fmt () -> ())
|
||||
(fun fmt digit -> Format.fprintf fmt "%a" Z.pp_print digit))
|
||||
(List.rev !digits)
|
||||
( if List.length !digits - leading_zeroes !digits = !Utils.Cli.max_prec_digits then "…"
|
||||
else "" )
|
||||
| LMoney e -> Format.fprintf fmt "$%.2f" Q.(to_float (of_bigint e / of_int 100))
|
||||
| LDate d ->
|
||||
Format.fprintf fmt "%s"
|
||||
(ODate.Unix.To.string (Option.get (ODate.Unix.To.generate_printer "%Y-%m-%d")) d)
|
||||
| LDuration d -> Format.fprintf fmt "%a days" Z.pp_print d
|
||||
|
||||
let format_op_kind (fmt : Format.formatter) (k : op_kind) =
|
||||
Format.fprintf fmt "%s"
|
||||
(match k with KInt -> "" | KRat -> "." | KMoney -> "$" | KDate -> "@" | KDuration -> "^")
|
||||
|
||||
let format_binop (fmt : Format.formatter) (op : binop Pos.marked) : unit =
|
||||
match Pos.unmark op with
|
||||
| Add k -> Format.fprintf fmt "+%a" format_op_kind k
|
||||
| Sub k -> Format.fprintf fmt "-%a" format_op_kind k
|
||||
| Mult k -> Format.fprintf fmt "*%a" format_op_kind k
|
||||
| Div k -> Format.fprintf fmt "/%a" format_op_kind k
|
||||
| And -> Format.fprintf fmt "%s" "&&"
|
||||
| Or -> Format.fprintf fmt "%s" "||"
|
||||
| Eq -> Format.fprintf fmt "%s" "=="
|
||||
| Neq -> Format.fprintf fmt "%s" "!="
|
||||
| Lt k -> Format.fprintf fmt "%s%a" "<" format_op_kind k
|
||||
| Lte k -> Format.fprintf fmt "%s%a" "<=" format_op_kind k
|
||||
| Gt k -> Format.fprintf fmt "%s%a" ">" format_op_kind k
|
||||
| Gte k -> Format.fprintf fmt "%s%a" ">=" format_op_kind k
|
||||
| Map -> Format.fprintf fmt "map"
|
||||
|
||||
let format_ternop (fmt : Format.formatter) (op : ternop Pos.marked) : unit =
|
||||
match Pos.unmark op with Fold -> Format.fprintf fmt "fold"
|
||||
|
||||
let format_log_entry (fmt : Format.formatter) (entry : log_entry) : unit =
|
||||
Format.fprintf fmt "%s"
|
||||
( match entry with
|
||||
| VarDef -> "Defining variable"
|
||||
| BeginCall -> "Calling subscope"
|
||||
| EndCall -> "Returned from subscope" )
|
||||
|
||||
let format_unop (fmt : Format.formatter) (op : unop Pos.marked) : unit =
|
||||
Format.fprintf fmt "%s"
|
||||
( match Pos.unmark op with
|
||||
| Minus _ -> "-"
|
||||
| Not -> "~"
|
||||
| ErrorOnEmpty -> "error_empty"
|
||||
| Log (entry, infos) ->
|
||||
Format.asprintf "log@[<hov 2>[%a|%a]@]" format_log_entry entry
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ".")
|
||||
(fun fmt info -> Utils.Uid.MarkedString.format_info fmt info))
|
||||
infos
|
||||
| Length -> "length"
|
||||
| IntToRat -> "int_to_rat" )
|
||||
|
||||
let needs_parens (e : expr Pos.marked) : bool =
|
||||
match Pos.unmark e with EAbs _ | EApp _ -> true | _ -> false
|
||||
|
||||
let format_var (fmt : Format.formatter) (v : Var.t) : unit =
|
||||
Format.fprintf fmt "%s" (Bindlib.name_of v)
|
||||
|
||||
let rec format_expr (fmt : Format.formatter) (e : expr Pos.marked) : unit =
|
||||
let format_with_parens (fmt : Format.formatter) (e : expr Pos.marked) =
|
||||
if needs_parens e then Format.fprintf fmt "(%a)" format_expr e
|
||||
else Format.fprintf fmt "%a" format_expr e
|
||||
in
|
||||
match Pos.unmark e with
|
||||
| EVar v -> Format.fprintf fmt "%a" format_var (Pos.unmark v)
|
||||
| ETuple es ->
|
||||
Format.fprintf fmt "@[<hov 2>(%a)@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ")
|
||||
(fun fmt (e, struct_field) ->
|
||||
match struct_field with
|
||||
| Some struct_field ->
|
||||
Format.fprintf fmt "@[<hov 2>\"%a\":@ %a@]" Uid.MarkedString.format_info
|
||||
struct_field format_expr e
|
||||
| None -> Format.fprintf fmt "@[%a@]" format_expr e))
|
||||
es
|
||||
| EArray es ->
|
||||
Format.fprintf fmt "@[<hov 2>[%a]@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ";@ ")
|
||||
(fun fmt e -> Format.fprintf fmt "@[%a@]" format_expr e))
|
||||
es
|
||||
| ETupleAccess (e1, n, i) -> (
|
||||
match i with
|
||||
| None -> Format.fprintf fmt "%a.%d" format_expr e1 n
|
||||
| Some i -> Format.fprintf fmt "%a.\"%a\"" format_expr e1 Uid.MarkedString.format_info i )
|
||||
| EInj (e, _n, i, _ts) -> Format.fprintf fmt "%a@ %a" Uid.MarkedString.format_info i format_expr e
|
||||
| EMatch (e, es) ->
|
||||
Format.fprintf fmt "@[<hov 2>match@ %a@ with@ %a@]" format_expr e
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "@ |@ ")
|
||||
(fun fmt (e, c) ->
|
||||
Format.fprintf fmt "%a@ %a" Uid.MarkedString.format_info c format_expr e))
|
||||
es
|
||||
| ELit l -> Format.fprintf fmt "%a" format_lit (Pos.same_pos_as l e)
|
||||
| EApp ((EAbs (_, binder, taus), _), args) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
let xs_tau = List.map2 (fun x tau -> (x, tau)) (Array.to_list xs) taus in
|
||||
let xs_tau_arg = List.map2 (fun (x, tau) arg -> (x, tau, arg)) xs_tau args in
|
||||
Format.fprintf fmt "@[%a%a@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "@ ")
|
||||
(fun fmt (x, tau, arg) ->
|
||||
Format.fprintf fmt "@[@[<hov 2>let@ %a@ :@ %a@ =@ %a@]@ in@\n@]" format_var x
|
||||
format_typ tau format_expr arg))
|
||||
xs_tau_arg format_expr body
|
||||
| EAbs (_, binder, taus) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
let xs_tau = List.map2 (fun x tau -> (x, tau)) (Array.to_list xs) taus in
|
||||
Format.fprintf fmt "@[<hov 2>λ@ %a →@ %a@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "@ ")
|
||||
(fun fmt (x, tau) ->
|
||||
Format.fprintf fmt "@[<hov 2>(%a:@ %a)@]" format_var x format_typ tau))
|
||||
xs_tau format_expr body
|
||||
| EApp ((EOp (Binop op), _), [ arg1; arg2 ]) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a@ %a@ %a@]" format_with_parens arg1 format_binop
|
||||
(op, Pos.no_pos) format_with_parens arg2
|
||||
| EApp ((EOp (Unop op), _), [ arg1 ]) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a@ %a@]" format_unop (op, Pos.no_pos) format_with_parens arg1
|
||||
| EApp (f, args) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a@ %a@]" format_expr f
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt "@ ") format_with_parens)
|
||||
args
|
||||
| EIfThenElse (e1, e2, e3) ->
|
||||
Format.fprintf fmt "if@ @[<hov 2>%a@]@ then@ @[<hov 2>%a@]@ else@ @[<hov 2>%a@]" format_expr
|
||||
e1 format_expr e2 format_expr e3
|
||||
| EOp (Ternop op) -> Format.fprintf fmt "%a" format_ternop (op, Pos.no_pos)
|
||||
| EOp (Binop op) -> Format.fprintf fmt "%a" format_binop (op, Pos.no_pos)
|
||||
| EOp (Unop op) -> Format.fprintf fmt "%a" format_unop (op, Pos.no_pos)
|
||||
| EDefault (exceptions, just, cons) ->
|
||||
if List.length exceptions = 0 then
|
||||
Format.fprintf fmt "@[<hov 2>⟨%a@ ⊢@ %a⟩@]" format_expr just format_expr cons
|
||||
else
|
||||
Format.fprintf fmt "@[<hov 2>⟨%a@ |@ %a@ ⊢@ %a@ ⟩@]"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") format_expr)
|
||||
exceptions format_expr just format_expr cons
|
||||
| EAssert e' -> Format.fprintf fmt "@[<hov 2>assert@ (%a)@]" format_expr e'
|
446
src/catala/default_calculus/typing.ml
Normal file
@ -0,0 +1,446 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Typing for the default calculus. Because of the error terms, we perform type inference using the
|
||||
classical W algorithm with union-find unification. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module A = Ast
|
||||
module Cli = Utils.Cli
|
||||
|
||||
(** {1 Types and unification} *)
|
||||
|
||||
module Any =
|
||||
Utils.Uid.Make
|
||||
(struct
|
||||
type info = unit
|
||||
|
||||
let format_info fmt () = Format.fprintf fmt "any"
|
||||
end)
|
||||
()
|
||||
|
||||
(** We do not reuse {!type: Dcalc.Ast.typ} because we have to include a new [TAny] variant. Indeed,
|
||||
error terms can have any type and this has to be captured by the type sytem. *)
|
||||
type typ =
|
||||
| TLit of A.typ_lit
|
||||
| TArrow of typ Pos.marked UnionFind.elem * typ Pos.marked UnionFind.elem
|
||||
| TTuple of typ Pos.marked UnionFind.elem list
|
||||
| TEnum of typ Pos.marked UnionFind.elem list
|
||||
| TArray of typ Pos.marked UnionFind.elem
|
||||
| TAny of Any.t
|
||||
|
||||
let typ_needs_parens (t : typ Pos.marked UnionFind.elem) : bool =
|
||||
let t = UnionFind.get (UnionFind.find t) in
|
||||
match Pos.unmark t with TArrow _ | TArray _ -> true | _ -> false
|
||||
|
||||
let rec format_typ (fmt : Format.formatter) (typ : typ Pos.marked UnionFind.elem) : unit =
|
||||
let format_typ_with_parens (fmt : Format.formatter) (t : typ Pos.marked UnionFind.elem) =
|
||||
if typ_needs_parens t then Format.fprintf fmt "(%a)" format_typ t
|
||||
else Format.fprintf fmt "%a" format_typ t
|
||||
in
|
||||
let typ = UnionFind.get (UnionFind.find typ) in
|
||||
match Pos.unmark typ with
|
||||
| TLit l -> Format.fprintf fmt "%a" Print.format_tlit l
|
||||
| TTuple ts ->
|
||||
Format.fprintf fmt "(%a)"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt " *@ ") format_typ)
|
||||
ts
|
||||
| TEnum ts ->
|
||||
Format.fprintf fmt "(%a)"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt " +@ ") format_typ)
|
||||
ts
|
||||
| TArrow (t1, t2) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a →@ %a@]" format_typ_with_parens t1 format_typ t2
|
||||
| TArray t1 -> Format.fprintf fmt "@[%a@ array@]" format_typ t1
|
||||
| TAny d -> Format.fprintf fmt "any[%d]" (Any.hash d)
|
||||
|
||||
(** Raises an error if unification cannot be performed *)
|
||||
let rec unify (t1 : typ Pos.marked UnionFind.elem) (t2 : typ Pos.marked UnionFind.elem) : unit =
|
||||
(* Cli.debug_print (Format.asprintf "Unifying %a and %a" format_typ t1 format_typ t2); *)
|
||||
let t1_repr = UnionFind.get (UnionFind.find t1) in
|
||||
let t2_repr = UnionFind.get (UnionFind.find t2) in
|
||||
let repr =
|
||||
match (t1_repr, t2_repr) with
|
||||
| (TLit tl1, _), (TLit tl2, _) when tl1 = tl2 -> None
|
||||
| (TArrow (t11, t12), _), (TArrow (t21, t22), _) ->
|
||||
unify t11 t21;
|
||||
unify t12 t22;
|
||||
None
|
||||
| (TTuple ts1, _), (TTuple ts2, _) ->
|
||||
List.iter2 unify ts1 ts2;
|
||||
None
|
||||
| (TEnum ts1, _), (TEnum ts2, _) ->
|
||||
List.iter2 unify ts1 ts2;
|
||||
None
|
||||
| (TArray t1', _), (TArray t2', _) ->
|
||||
unify t1' t2';
|
||||
None
|
||||
| (TAny _, _), (TAny _, _) -> None
|
||||
| (TAny _, _), t_repr | t_repr, (TAny _, _) -> Some t_repr
|
||||
| (_, t1_pos), (_, t2_pos) ->
|
||||
(* TODO: if we get weird error messages, then it means that we should use the persistent
|
||||
version of the union-find data structure. *)
|
||||
Errors.raise_multispanned_error
|
||||
(Format.asprintf "Error during typechecking, types %a and %a are incompatible" format_typ
|
||||
t1 format_typ t2)
|
||||
[
|
||||
(Some (Format.asprintf "Type %a coming from expression:" format_typ t1), t1_pos);
|
||||
(Some (Format.asprintf "Type %a coming from expression:" format_typ t2), t2_pos);
|
||||
]
|
||||
in
|
||||
let t_union = UnionFind.union t1 t2 in
|
||||
match repr with None -> () | Some t_repr -> UnionFind.set t_union t_repr
|
||||
|
||||
(** Operators have a single type, instead of being polymorphic with constraints. This allows us to
|
||||
have a simpler type system, while we argue the syntactic burden of operator annotations helps
|
||||
the programmer visualize the type flow in the code. *)
|
||||
let op_type (op : A.operator Pos.marked) : typ Pos.marked UnionFind.elem =
|
||||
let pos = Pos.get_position op in
|
||||
let bt = UnionFind.make (TLit TBool, pos) in
|
||||
let it = UnionFind.make (TLit TInt, pos) in
|
||||
let rt = UnionFind.make (TLit TRat, pos) in
|
||||
let mt = UnionFind.make (TLit TMoney, pos) in
|
||||
let dut = UnionFind.make (TLit TDuration, pos) in
|
||||
let dat = UnionFind.make (TLit TDate, pos) in
|
||||
let any = UnionFind.make (TAny (Any.fresh ()), pos) in
|
||||
let array_any = UnionFind.make (TArray any, pos) in
|
||||
let any2 = UnionFind.make (TAny (Any.fresh ()), pos) in
|
||||
let arr x y = UnionFind.make (TArrow (x, y), pos) in
|
||||
match Pos.unmark op with
|
||||
| A.Ternop A.Fold -> arr (arr any2 (arr any any2)) (arr any2 (arr array_any any2))
|
||||
| A.Binop (A.And | A.Or) -> arr bt (arr bt bt)
|
||||
| A.Binop (A.Add KInt | A.Sub KInt | A.Mult KInt | A.Div KInt) -> arr it (arr it it)
|
||||
| A.Binop (A.Add KRat | A.Sub KRat | A.Mult KRat | A.Div KRat) -> arr rt (arr rt rt)
|
||||
| A.Binop (A.Add KMoney | A.Sub KMoney) -> arr mt (arr mt mt)
|
||||
| A.Binop (A.Add KDuration | A.Sub KDuration) -> arr dut (arr dut dut)
|
||||
| A.Binop (A.Sub KDate) -> arr dat (arr dat dut)
|
||||
| A.Binop (A.Add KDate) -> arr dat (arr dut dat)
|
||||
| A.Binop (A.Div KMoney) -> arr mt (arr mt rt)
|
||||
| A.Binop (A.Mult KMoney) -> arr mt (arr rt mt)
|
||||
| A.Binop (A.Lt KInt | A.Lte KInt | A.Gt KInt | A.Gte KInt) -> arr it (arr it bt)
|
||||
| A.Binop (A.Lt KRat | A.Lte KRat | A.Gt KRat | A.Gte KRat) -> arr rt (arr rt bt)
|
||||
| A.Binop (A.Lt KMoney | A.Lte KMoney | A.Gt KMoney | A.Gte KMoney) -> arr mt (arr mt bt)
|
||||
| A.Binop (A.Lt KDate | A.Lte KDate | A.Gt KDate | A.Gte KDate) -> arr dat (arr dat bt)
|
||||
| A.Binop (A.Lt KDuration | A.Lte KDuration | A.Gt KDuration | A.Gte KDuration) ->
|
||||
arr dut (arr dut bt)
|
||||
| A.Binop (A.Eq | A.Neq) -> arr any (arr any bt)
|
||||
| A.Binop A.Map -> arr (arr any any2) (arr array_any any2)
|
||||
| A.Unop (A.Minus KInt) -> arr it it
|
||||
| A.Unop (A.Minus KRat) -> arr rt rt
|
||||
| A.Unop (A.Minus KMoney) -> arr mt mt
|
||||
| A.Unop (A.Minus KDuration) -> arr dut dut
|
||||
| A.Unop A.Not -> arr bt bt
|
||||
| A.Unop A.ErrorOnEmpty -> arr any any
|
||||
| A.Unop (A.Log _) -> arr any any
|
||||
| A.Unop A.Length -> arr array_any it
|
||||
| A.Unop A.IntToRat -> arr it rt
|
||||
| Binop (Mult (KDate | KDuration)) | Binop (Div (KDate | KDuration)) | Unop (Minus KDate) ->
|
||||
Errors.raise_spanned_error "This operator is not available!" pos
|
||||
|
||||
let rec ast_to_typ (ty : A.typ) : typ =
|
||||
match ty with
|
||||
| A.TLit l -> TLit l
|
||||
| A.TArrow (t1, t2) ->
|
||||
TArrow
|
||||
( UnionFind.make (Pos.map_under_mark ast_to_typ t1),
|
||||
UnionFind.make (Pos.map_under_mark ast_to_typ t2) )
|
||||
| A.TTuple ts -> TTuple (List.map (fun t -> UnionFind.make (Pos.map_under_mark ast_to_typ t)) ts)
|
||||
| A.TEnum ts -> TEnum (List.map (fun t -> UnionFind.make (Pos.map_under_mark ast_to_typ t)) ts)
|
||||
| A.TArray t -> TArray (UnionFind.make (Pos.map_under_mark ast_to_typ t))
|
||||
| A.TAny -> TAny (Any.fresh ())
|
||||
|
||||
let rec typ_to_ast (ty : typ Pos.marked UnionFind.elem) : A.typ Pos.marked =
|
||||
Pos.map_under_mark
|
||||
(fun ty ->
|
||||
match ty with
|
||||
| TLit l -> A.TLit l
|
||||
| TTuple ts -> A.TTuple (List.map typ_to_ast ts)
|
||||
| TEnum ts -> A.TEnum (List.map typ_to_ast ts)
|
||||
| TArrow (t1, t2) -> A.TArrow (typ_to_ast t1, typ_to_ast t2)
|
||||
| TAny _ -> A.TAny
|
||||
| TArray t1 -> A.TArray (typ_to_ast t1))
|
||||
(UnionFind.get (UnionFind.find ty))
|
||||
|
||||
(** {1 Double-directed typing} *)
|
||||
|
||||
type env = typ Pos.marked UnionFind.elem A.VarMap.t
|
||||
|
||||
(** Infers the most permissive type from an expression *)
|
||||
let rec typecheck_expr_bottom_up (env : env) (e : A.expr Pos.marked) : typ Pos.marked UnionFind.elem
|
||||
=
|
||||
let out =
|
||||
match Pos.unmark e with
|
||||
| EVar v -> (
|
||||
match A.VarMap.find_opt (Pos.unmark v) env with
|
||||
| Some t -> t
|
||||
| None ->
|
||||
Errors.raise_spanned_error "Variable not found in the current context"
|
||||
(Pos.get_position e) )
|
||||
| ELit (LBool _) -> UnionFind.make (Pos.same_pos_as (TLit TBool) e)
|
||||
| ELit (LInt _) -> UnionFind.make (Pos.same_pos_as (TLit TInt) e)
|
||||
| ELit (LRat _) -> UnionFind.make (Pos.same_pos_as (TLit TRat) e)
|
||||
| ELit (LMoney _) -> UnionFind.make (Pos.same_pos_as (TLit TMoney) e)
|
||||
| ELit (LDate _) -> UnionFind.make (Pos.same_pos_as (TLit TDate) e)
|
||||
| ELit (LDuration _) -> UnionFind.make (Pos.same_pos_as (TLit TDuration) e)
|
||||
| ELit LUnit -> UnionFind.make (Pos.same_pos_as (TLit TUnit) e)
|
||||
| ELit LEmptyError -> UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e)
|
||||
| ETuple es ->
|
||||
let ts = List.map (fun (e, _) -> typecheck_expr_bottom_up env e) es in
|
||||
UnionFind.make (Pos.same_pos_as (TTuple ts) e)
|
||||
| ETupleAccess (e1, n, _) -> (
|
||||
let t1 = typecheck_expr_bottom_up env e1 in
|
||||
match Pos.unmark (UnionFind.get (UnionFind.find t1)) with
|
||||
| TTuple ts -> (
|
||||
match List.nth_opt ts n with
|
||||
| Some t' -> t'
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"Expression should have a tuple type with at least %d elements but only has %d"
|
||||
n (List.length ts))
|
||||
(Pos.get_position e1) )
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "Expected a tuple, got a %a" format_typ t1)
|
||||
(Pos.get_position e1) )
|
||||
| EInj (e1, n, _, ts) ->
|
||||
let ts = List.map (fun t -> UnionFind.make (Pos.map_under_mark ast_to_typ t)) ts in
|
||||
let ts_n =
|
||||
match List.nth_opt ts n with
|
||||
| Some ts_n -> ts_n
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"Expression should have a sum type with at least %d cases but only has %d" n
|
||||
(List.length ts))
|
||||
(Pos.get_position e)
|
||||
in
|
||||
typecheck_expr_top_down env e1 ts_n;
|
||||
UnionFind.make (Pos.same_pos_as (TEnum ts) e)
|
||||
| EMatch (e1, es) ->
|
||||
let enum_cases =
|
||||
List.map (fun (e', _) -> UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e')) es
|
||||
in
|
||||
let t_e1 = UnionFind.make (Pos.same_pos_as (TEnum enum_cases) e1) in
|
||||
typecheck_expr_top_down env e1 t_e1;
|
||||
let t_ret = UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e) in
|
||||
List.iteri
|
||||
(fun i (es', _) ->
|
||||
let enum_t = List.nth enum_cases i in
|
||||
let t_es' = UnionFind.make (Pos.same_pos_as (TArrow (enum_t, t_ret)) es') in
|
||||
typecheck_expr_top_down env es' t_es')
|
||||
es;
|
||||
t_ret
|
||||
| EAbs (pos_binder, binder, taus) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
if Array.length xs = List.length taus then
|
||||
let xstaus =
|
||||
List.map2
|
||||
(fun x tau -> (x, UnionFind.make (ast_to_typ (Pos.unmark tau), Pos.get_position tau)))
|
||||
(Array.to_list xs) taus
|
||||
in
|
||||
let env = List.fold_left (fun env (x, tau) -> A.VarMap.add x tau env) env xstaus in
|
||||
List.fold_right
|
||||
(fun (_, t_arg) (acc : typ Pos.marked UnionFind.elem) ->
|
||||
UnionFind.make (TArrow (t_arg, acc), pos_binder))
|
||||
xstaus
|
||||
(typecheck_expr_bottom_up env body)
|
||||
else
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "function has %d variables but was supplied %d types" (Array.length xs)
|
||||
(List.length taus))
|
||||
pos_binder
|
||||
| EApp (e1, args) ->
|
||||
let t_args = List.map (typecheck_expr_bottom_up env) args in
|
||||
let t_ret = UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e) in
|
||||
let t_app =
|
||||
List.fold_right
|
||||
(fun t_arg acc -> UnionFind.make (Pos.same_pos_as (TArrow (t_arg, acc)) e))
|
||||
t_args t_ret
|
||||
in
|
||||
typecheck_expr_top_down env e1 t_app;
|
||||
t_ret
|
||||
| EOp op -> op_type (Pos.same_pos_as op e)
|
||||
| EDefault (excepts, just, cons) ->
|
||||
typecheck_expr_top_down env just (UnionFind.make (Pos.same_pos_as (TLit TBool) just));
|
||||
let tcons = typecheck_expr_bottom_up env cons in
|
||||
List.iter (fun except -> typecheck_expr_top_down env except tcons) excepts;
|
||||
tcons
|
||||
| EIfThenElse (cond, et, ef) ->
|
||||
typecheck_expr_top_down env cond (UnionFind.make (Pos.same_pos_as (TLit TBool) cond));
|
||||
let tt = typecheck_expr_bottom_up env et in
|
||||
typecheck_expr_top_down env ef tt;
|
||||
tt
|
||||
| EAssert e' ->
|
||||
typecheck_expr_top_down env e' (UnionFind.make (Pos.same_pos_as (TLit TBool) e'));
|
||||
UnionFind.make (Pos.same_pos_as (TLit TUnit) e')
|
||||
| EArray es ->
|
||||
let cell_type = UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e) in
|
||||
List.iter
|
||||
(fun e' ->
|
||||
let t_e' = typecheck_expr_bottom_up env e' in
|
||||
unify cell_type t_e')
|
||||
es;
|
||||
UnionFind.make (Pos.same_pos_as (TArray cell_type) e)
|
||||
in
|
||||
(* Cli.debug_print (Format.asprintf "Found type of %a: %a" Print.format_expr e format_typ out); *)
|
||||
out
|
||||
|
||||
(** Checks whether the expression can be typed with the provided type *)
|
||||
and typecheck_expr_top_down (env : env) (e : A.expr Pos.marked)
|
||||
(tau : typ Pos.marked UnionFind.elem) : unit =
|
||||
(* Cli.debug_print (Format.asprintf "Typechecking %a : %a" Print.format_expr e format_typ tau); *)
|
||||
match Pos.unmark e with
|
||||
| EVar v -> (
|
||||
match A.VarMap.find_opt (Pos.unmark v) env with
|
||||
| Some tau' -> ignore (unify tau tau')
|
||||
| None ->
|
||||
Errors.raise_spanned_error "Variable not found in the current context"
|
||||
(Pos.get_position e) )
|
||||
| ELit (LBool _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TBool) e))
|
||||
| ELit (LInt _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TInt) e))
|
||||
| ELit (LRat _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TRat) e))
|
||||
| ELit (LMoney _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TMoney) e))
|
||||
| ELit (LDate _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TDate) e))
|
||||
| ELit (LDuration _) -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TDuration) e))
|
||||
| ELit LUnit -> unify tau (UnionFind.make (Pos.same_pos_as (TLit TUnit) e))
|
||||
| ELit LEmptyError -> unify tau (UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e))
|
||||
| ETuple es -> (
|
||||
let tau' = UnionFind.get (UnionFind.find tau) in
|
||||
match Pos.unmark tau' with
|
||||
| TTuple ts -> List.iter2 (fun (e, _) t -> typecheck_expr_top_down env e t) es ts
|
||||
| TAny _ ->
|
||||
unify tau
|
||||
(UnionFind.make
|
||||
(Pos.same_pos_as
|
||||
(TTuple (List.map (fun (arg, _) -> typecheck_expr_bottom_up env arg) es))
|
||||
e))
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "expected %a, got a tuple" format_typ tau)
|
||||
(Pos.get_position e) )
|
||||
| ETupleAccess (e1, n, _) -> (
|
||||
let t1 = typecheck_expr_bottom_up env e1 in
|
||||
match Pos.unmark (UnionFind.get (UnionFind.find t1)) with
|
||||
| TTuple t1s -> (
|
||||
match List.nth_opt t1s n with
|
||||
| Some t1n -> unify t1n tau
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"expression should have a tuple type with at least %d elements but only has %d" n
|
||||
(List.length t1s))
|
||||
(Pos.get_position e1) )
|
||||
| TAny _ ->
|
||||
(* Include total number of cases in ETupleAccess to continue typechecking at this point *)
|
||||
Errors.raise_spanned_error
|
||||
"The precise type of this expression cannot be inferred.\n\
|
||||
Please raise an issue one https://github.com/CatalaLang/catala/issues"
|
||||
(Pos.get_position e1)
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "expected a tuple , got %a" format_typ tau)
|
||||
(Pos.get_position e) )
|
||||
| EInj (e1, n, _, ts) ->
|
||||
let ts = List.map (fun t -> UnionFind.make (Pos.map_under_mark ast_to_typ t)) ts in
|
||||
let ts_n =
|
||||
match List.nth_opt ts n with
|
||||
| Some ts_n -> ts_n
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"Expression should have a sum type with at least %d cases but only has %d" n
|
||||
(List.length ts))
|
||||
(Pos.get_position e)
|
||||
in
|
||||
typecheck_expr_top_down env e1 ts_n;
|
||||
unify (UnionFind.make (Pos.same_pos_as (TEnum ts) e)) tau
|
||||
| EMatch (e1, es) ->
|
||||
let enum_cases =
|
||||
List.map (fun (e', _) -> UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e')) es
|
||||
in
|
||||
let t_e1 = UnionFind.make (Pos.same_pos_as (TEnum enum_cases) e1) in
|
||||
typecheck_expr_top_down env e1 t_e1;
|
||||
let t_ret = UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e) in
|
||||
List.iteri
|
||||
(fun i (es', _) ->
|
||||
let enum_t = List.nth enum_cases i in
|
||||
let t_es' = UnionFind.make (Pos.same_pos_as (TArrow (enum_t, t_ret)) es') in
|
||||
typecheck_expr_top_down env es' t_es')
|
||||
es;
|
||||
unify tau t_ret
|
||||
| EAbs (pos_binder, binder, t_args) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
if Array.length xs = List.length t_args then
|
||||
let xstaus =
|
||||
List.map2
|
||||
(fun x t_arg -> (x, UnionFind.make (Pos.map_under_mark ast_to_typ t_arg)))
|
||||
(Array.to_list xs) t_args
|
||||
in
|
||||
let env = List.fold_left (fun env (x, t_arg) -> A.VarMap.add x t_arg env) env xstaus in
|
||||
let t_out = typecheck_expr_bottom_up env body in
|
||||
let t_func =
|
||||
List.fold_right
|
||||
(fun (_, t_arg) acc -> UnionFind.make (Pos.same_pos_as (TArrow (t_arg, acc)) e))
|
||||
xstaus t_out
|
||||
in
|
||||
unify t_func tau
|
||||
else
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "function has %d variables but was supplied %d types" (Array.length xs)
|
||||
(List.length t_args))
|
||||
pos_binder
|
||||
| EApp (e1, args) ->
|
||||
let t_args = List.map (typecheck_expr_bottom_up env) args in
|
||||
let te1 = typecheck_expr_bottom_up env e1 in
|
||||
let t_func =
|
||||
List.fold_right
|
||||
(fun t_arg acc -> UnionFind.make (Pos.same_pos_as (TArrow (t_arg, acc)) e))
|
||||
t_args tau
|
||||
in
|
||||
unify te1 t_func
|
||||
| EOp op ->
|
||||
let op_typ = op_type (Pos.same_pos_as op e) in
|
||||
unify op_typ tau
|
||||
| EDefault (excepts, just, cons) ->
|
||||
typecheck_expr_top_down env just (UnionFind.make (Pos.same_pos_as (TLit TBool) just));
|
||||
typecheck_expr_top_down env cons tau;
|
||||
List.iter (fun except -> typecheck_expr_top_down env except tau) excepts
|
||||
| EIfThenElse (cond, et, ef) ->
|
||||
typecheck_expr_top_down env cond (UnionFind.make (Pos.same_pos_as (TLit TBool) cond));
|
||||
typecheck_expr_top_down env et tau;
|
||||
typecheck_expr_top_down env ef tau
|
||||
| EAssert e' ->
|
||||
typecheck_expr_top_down env e' (UnionFind.make (Pos.same_pos_as (TLit TBool) e'));
|
||||
unify tau (UnionFind.make (Pos.same_pos_as (TLit TUnit) e'))
|
||||
| EArray es ->
|
||||
let cell_type = UnionFind.make (Pos.same_pos_as (TAny (Any.fresh ())) e) in
|
||||
List.iter
|
||||
(fun e' ->
|
||||
let t_e' = typecheck_expr_bottom_up env e' in
|
||||
unify cell_type t_e')
|
||||
es;
|
||||
unify tau (UnionFind.make (Pos.same_pos_as (TArray cell_type) e))
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
(* Infer the type of an expression *)
|
||||
let infer_type (e : A.expr Pos.marked) : A.typ Pos.marked =
|
||||
let ty = typecheck_expr_bottom_up A.VarMap.empty e in
|
||||
typ_to_ast ty
|
||||
|
||||
(** Typechecks an expression given an expected type *)
|
||||
let check_type (e : A.expr Pos.marked) (tau : A.typ Pos.marked) =
|
||||
typecheck_expr_top_down A.VarMap.empty e (UnionFind.make (Pos.map_under_mark ast_to_typ tau))
|
146
src/catala/desugared/ast.ml
Normal file
@ -0,0 +1,146 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Abstract syntax tree of the desugared representation *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Uid = Utils.Uid
|
||||
|
||||
(** {1 Names, Maps and Keys} *)
|
||||
|
||||
module IdentMap : Map.S with type key = String.t = Map.Make (String)
|
||||
|
||||
module RuleName : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module RuleMap : Map.S with type key = RuleName.t = Map.Make (RuleName)
|
||||
|
||||
module RuleSet : Set.S with type elt = RuleName.t = Set.Make (RuleName)
|
||||
|
||||
(** Inside a scope, a definition can refer either to a scope def, or a subscope def *)
|
||||
module ScopeDef = struct
|
||||
type t =
|
||||
| Var of Scopelang.Ast.ScopeVar.t
|
||||
| SubScopeVar of Scopelang.Ast.SubScopeName.t * Scopelang.Ast.ScopeVar.t
|
||||
(** In this case, the [Uid.Var.t] lives inside the context of the subscope's original
|
||||
declaration *)
|
||||
|
||||
let compare x y =
|
||||
match (x, y) with
|
||||
| Var x, Var y | Var x, SubScopeVar (_, y) | SubScopeVar (_, x), Var y ->
|
||||
Scopelang.Ast.ScopeVar.compare x y
|
||||
| SubScopeVar (_, x), SubScopeVar (_, y) -> Scopelang.Ast.ScopeVar.compare x y
|
||||
|
||||
let get_position x =
|
||||
match x with
|
||||
| Var x -> Pos.get_position (Scopelang.Ast.ScopeVar.get_info x)
|
||||
| SubScopeVar (x, _) -> Pos.get_position (Scopelang.Ast.SubScopeName.get_info x)
|
||||
|
||||
let format_t fmt x =
|
||||
match x with
|
||||
| Var v -> Scopelang.Ast.ScopeVar.format_t fmt v
|
||||
| SubScopeVar (s, v) ->
|
||||
Format.fprintf fmt "%a.%a" Scopelang.Ast.SubScopeName.format_t s
|
||||
Scopelang.Ast.ScopeVar.format_t v
|
||||
|
||||
let hash x =
|
||||
match x with
|
||||
| Var v -> Scopelang.Ast.ScopeVar.hash v
|
||||
| SubScopeVar (_, v) -> Scopelang.Ast.ScopeVar.hash v
|
||||
end
|
||||
|
||||
module ScopeDefMap : Map.S with type key = ScopeDef.t = Map.Make (ScopeDef)
|
||||
|
||||
module ScopeDefSet : Set.S with type elt = ScopeDef.t = Set.Make (ScopeDef)
|
||||
|
||||
(** {1 AST} *)
|
||||
|
||||
type rule = {
|
||||
just : Scopelang.Ast.expr Pos.marked Bindlib.box;
|
||||
cons : Scopelang.Ast.expr Pos.marked Bindlib.box;
|
||||
parameter : (Scopelang.Ast.Var.t * Scopelang.Ast.typ Pos.marked) option;
|
||||
exception_to_rule : RuleName.t option;
|
||||
}
|
||||
|
||||
let empty_rule (pos : Pos.t) (have_parameter : Scopelang.Ast.typ Pos.marked option) : rule =
|
||||
{
|
||||
just = Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool false), pos);
|
||||
cons = Bindlib.box (Scopelang.Ast.ELit Dcalc.Ast.LEmptyError, pos);
|
||||
parameter =
|
||||
( match have_parameter with
|
||||
| Some typ -> Some (Scopelang.Ast.Var.make ("dummy", pos), typ)
|
||||
| None -> None );
|
||||
exception_to_rule = None;
|
||||
}
|
||||
|
||||
let always_false_rule (pos : Pos.t) (have_parameter : Scopelang.Ast.typ Pos.marked option) : rule =
|
||||
{
|
||||
just = Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool true), pos);
|
||||
cons = Bindlib.box (Scopelang.Ast.ELit (Dcalc.Ast.LBool false), pos);
|
||||
parameter =
|
||||
( match have_parameter with
|
||||
| Some typ -> Some (Scopelang.Ast.Var.make ("dummy", pos), typ)
|
||||
| None -> None );
|
||||
exception_to_rule = None;
|
||||
}
|
||||
|
||||
type assertion = Scopelang.Ast.expr Pos.marked Bindlib.box
|
||||
|
||||
type variation_typ = Increasing | Decreasing
|
||||
|
||||
type reference_typ = Decree | Law
|
||||
|
||||
type meta_assertion =
|
||||
| FixedBy of reference_typ Pos.marked
|
||||
| VariesWith of unit * variation_typ Pos.marked option
|
||||
|
||||
type scope = {
|
||||
scope_vars : Scopelang.Ast.ScopeVarSet.t;
|
||||
scope_sub_scopes : Scopelang.Ast.ScopeName.t Scopelang.Ast.SubScopeMap.t;
|
||||
scope_uid : Scopelang.Ast.ScopeName.t;
|
||||
scope_defs :
|
||||
(rule RuleMap.t * Scopelang.Ast.typ Pos.marked * bool) (* is it a condition? *) ScopeDefMap.t;
|
||||
scope_assertions : assertion list;
|
||||
scope_meta_assertions : meta_assertion list;
|
||||
}
|
||||
|
||||
type program = {
|
||||
program_scopes : scope Scopelang.Ast.ScopeMap.t;
|
||||
program_enums : Scopelang.Ast.enum_ctx;
|
||||
program_structs : Scopelang.Ast.struct_ctx;
|
||||
}
|
||||
|
||||
(** {1 Helpers} *)
|
||||
|
||||
let free_variables (def : rule RuleMap.t) : Pos.t ScopeDefMap.t =
|
||||
let add_locs (acc : Pos.t ScopeDefMap.t) (locs : Scopelang.Ast.LocationSet.t) :
|
||||
Pos.t ScopeDefMap.t =
|
||||
Scopelang.Ast.LocationSet.fold
|
||||
(fun (loc, loc_pos) acc ->
|
||||
ScopeDefMap.add
|
||||
( match loc with
|
||||
| Scopelang.Ast.ScopeVar v -> ScopeDef.Var (Pos.unmark v)
|
||||
| Scopelang.Ast.SubScopeVar (_, sub_index, sub_var) ->
|
||||
ScopeDef.SubScopeVar (Pos.unmark sub_index, Pos.unmark sub_var) )
|
||||
loc_pos acc)
|
||||
locs acc
|
||||
in
|
||||
RuleMap.fold
|
||||
(fun _ rule acc ->
|
||||
let locs =
|
||||
Scopelang.Ast.LocationSet.union
|
||||
(Scopelang.Ast.locations_used (Bindlib.unbox rule.just))
|
||||
(Scopelang.Ast.locations_used (Bindlib.unbox rule.cons))
|
||||
in
|
||||
add_locs acc locs)
|
||||
def ScopeDefMap.empty
|
181
src/catala/desugared/dependency.ml
Normal file
@ -0,0 +1,181 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Scope dependencies computations using {{:http://ocamlgraph.lri.fr/} OCamlgraph} *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
|
||||
(** {1 Graph declaration} *)
|
||||
|
||||
(** Vertices: scope variables or subscopes.
|
||||
|
||||
The vertices of the scope dependency graph are either :
|
||||
|
||||
- the variables of the scope ;
|
||||
- the subscopes of the scope.
|
||||
|
||||
Indeed, during interpretation, subscopes are executed atomically. *)
|
||||
module Vertex = struct
|
||||
type t = Var of Scopelang.Ast.ScopeVar.t | SubScope of Scopelang.Ast.SubScopeName.t
|
||||
|
||||
let hash x =
|
||||
match x with
|
||||
| Var x -> Scopelang.Ast.ScopeVar.hash x
|
||||
| SubScope x -> Scopelang.Ast.SubScopeName.hash x
|
||||
|
||||
let compare = compare
|
||||
|
||||
let equal x y =
|
||||
match (x, y) with
|
||||
| Var x, Var y -> Scopelang.Ast.ScopeVar.compare x y = 0
|
||||
| SubScope x, SubScope y -> Scopelang.Ast.SubScopeName.compare x y = 0
|
||||
| _ -> false
|
||||
|
||||
let format_t (fmt : Format.formatter) (x : t) : unit =
|
||||
match x with
|
||||
| Var v -> Scopelang.Ast.ScopeVar.format_t fmt v
|
||||
| SubScope v -> Scopelang.Ast.SubScopeName.format_t fmt v
|
||||
end
|
||||
|
||||
(** On the edges, the label is the position of the expression responsible for the use of the
|
||||
variable. In the graph, [x -> y] if [x] is used in the definition of [y].*)
|
||||
module Edge = struct
|
||||
type t = Pos.t
|
||||
|
||||
let compare = compare
|
||||
|
||||
let default = Pos.no_pos
|
||||
end
|
||||
|
||||
module ScopeDependencies = Graph.Persistent.Digraph.ConcreteBidirectionalLabeled (Vertex) (Edge)
|
||||
(** Module of the graph, provided by OCamlGraph *)
|
||||
|
||||
module TopologicalTraversal = Graph.Topological.Make (ScopeDependencies)
|
||||
(** Module of the topological traversal of the graph, provided by OCamlGraph *)
|
||||
|
||||
module SCC = Graph.Components.Make (ScopeDependencies)
|
||||
(** Tarjan's stongly connected components algorithm, provided by OCamlGraph *)
|
||||
|
||||
(** {1 Graph computations} *)
|
||||
|
||||
(** Returns an ordering of the scope variables and subscope compatible with the dependencies of the
|
||||
computation *)
|
||||
let correct_computation_ordering (g : ScopeDependencies.t) : Vertex.t list =
|
||||
List.rev (TopologicalTraversal.fold (fun sd acc -> sd :: acc) g [])
|
||||
|
||||
(** Outputs an error in case of cycles. *)
|
||||
let check_for_cycle (scope : Ast.scope) (g : ScopeDependencies.t) : unit =
|
||||
(* if there is a cycle, there will be an strongly connected component of cardinality > 1 *)
|
||||
let sccs = SCC.scc_list g in
|
||||
if List.length sccs < ScopeDependencies.nb_vertex g then
|
||||
let scc = List.find (fun scc -> List.length scc > 1) sccs in
|
||||
Errors.raise_multispanned_error
|
||||
(Format.asprintf "Cyclic dependency detected between variables of scope %a!"
|
||||
Scopelang.Ast.ScopeName.format_t scope.scope_uid)
|
||||
(List.flatten
|
||||
(List.map
|
||||
(fun v ->
|
||||
let var_str, var_info =
|
||||
match v with
|
||||
| Vertex.Var v ->
|
||||
( Format.asprintf "%a" Scopelang.Ast.ScopeVar.format_t v,
|
||||
Scopelang.Ast.ScopeVar.get_info v )
|
||||
| Vertex.SubScope v ->
|
||||
( Format.asprintf "%a" Scopelang.Ast.SubScopeName.format_t v,
|
||||
Scopelang.Ast.SubScopeName.get_info v )
|
||||
in
|
||||
let succs = ScopeDependencies.succ_e g v in
|
||||
let _, edge_pos, succ = List.find (fun (_, _, succ) -> List.mem succ scc) succs in
|
||||
let succ_str =
|
||||
match succ with
|
||||
| Vertex.Var v -> Format.asprintf "%a" Scopelang.Ast.ScopeVar.format_t v
|
||||
| Vertex.SubScope v -> Format.asprintf "%a" Scopelang.Ast.SubScopeName.format_t v
|
||||
in
|
||||
[
|
||||
(Some ("Cycle variable " ^ var_str ^ ", declared:"), Pos.get_position var_info);
|
||||
( Some ("Used here in the definition of another cycle variable " ^ succ_str ^ ":"),
|
||||
edge_pos );
|
||||
])
|
||||
scc))
|
||||
|
||||
(** Builds the dependency graph of a particular scope *)
|
||||
let build_scope_dependencies (scope : Ast.scope) : ScopeDependencies.t =
|
||||
let g = ScopeDependencies.empty in
|
||||
(* Add all the vertices to the graph *)
|
||||
let g =
|
||||
Scopelang.Ast.ScopeVarSet.fold
|
||||
(fun (v : Scopelang.Ast.ScopeVar.t) g -> ScopeDependencies.add_vertex g (Vertex.Var v))
|
||||
scope.scope_vars g
|
||||
in
|
||||
let g =
|
||||
Scopelang.Ast.SubScopeMap.fold
|
||||
(fun (v : Scopelang.Ast.SubScopeName.t) _ g ->
|
||||
ScopeDependencies.add_vertex g (Vertex.SubScope v))
|
||||
scope.scope_sub_scopes g
|
||||
in
|
||||
let g =
|
||||
Ast.ScopeDefMap.fold
|
||||
(fun def_key (def, _, _) g ->
|
||||
let fv = Ast.free_variables def in
|
||||
Ast.ScopeDefMap.fold
|
||||
(fun fv_def fv_def_pos g ->
|
||||
match (def_key, fv_def) with
|
||||
| Ast.ScopeDef.Var defined, Ast.ScopeDef.Var used ->
|
||||
(* simple case *)
|
||||
if used = defined then
|
||||
(* variable definitions cannot be recursive *)
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The variable %a is used in one of its definitions, but recursion is \
|
||||
forbidden in Catala"
|
||||
Scopelang.Ast.ScopeVar.format_t defined)
|
||||
fv_def_pos
|
||||
else
|
||||
let edge =
|
||||
ScopeDependencies.E.create (Vertex.Var used) fv_def_pos (Vertex.Var defined)
|
||||
in
|
||||
ScopeDependencies.add_edge_e g edge
|
||||
| Ast.ScopeDef.SubScopeVar (defined, _), Ast.ScopeDef.Var used ->
|
||||
(* here we are defining the input of a subscope using a var of the scope *)
|
||||
let edge =
|
||||
ScopeDependencies.E.create (Vertex.Var used) fv_def_pos (Vertex.SubScope defined)
|
||||
in
|
||||
ScopeDependencies.add_edge_e g edge
|
||||
| Ast.ScopeDef.SubScopeVar (defined, _), Ast.ScopeDef.SubScopeVar (used, _) ->
|
||||
(* here we are defining the input of a scope with the output of another subscope *)
|
||||
if used = defined then
|
||||
(* subscopes are not recursive functions *)
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The subscope %a is used when defining one of its inputs, but recursion is \
|
||||
forbidden in Catala"
|
||||
Scopelang.Ast.SubScopeName.format_t defined)
|
||||
fv_def_pos
|
||||
else
|
||||
let edge =
|
||||
ScopeDependencies.E.create (Vertex.SubScope used) fv_def_pos
|
||||
(Vertex.SubScope defined)
|
||||
in
|
||||
ScopeDependencies.add_edge_e g edge
|
||||
| Ast.ScopeDef.Var defined, Ast.ScopeDef.SubScopeVar (used, _) ->
|
||||
(* finally we define a scope var with the output of a subscope *)
|
||||
let edge =
|
||||
ScopeDependencies.E.create (Vertex.SubScope used) fv_def_pos (Vertex.Var defined)
|
||||
in
|
||||
ScopeDependencies.add_edge_e g edge)
|
||||
fv g)
|
||||
scope.scope_defs g
|
||||
in
|
||||
g
|
30
src/catala/desugared/desugared.mld
Normal file
@ -0,0 +1,30 @@
|
||||
{0 Desugared representation }
|
||||
|
||||
This representation is the second in the compilation chain
|
||||
(see {{: index.html#architecture} Architecture}). Its main difference
|
||||
with {{: surface.html} the surface representation} is that the legislative
|
||||
text has been discarded and all the definitions of each variables have been
|
||||
collected in the same place rather than being scattered across the code base.
|
||||
|
||||
The module describing the abstract syntax tree is:
|
||||
|
||||
{!modules: Desugared.Ast}
|
||||
|
||||
{1 Translation to the scope language}
|
||||
|
||||
Related modules:
|
||||
|
||||
{!modules: Desugared.Dependency Desugared.Desugared_to_scope}
|
||||
|
||||
Before the translation to the {{: scopelang.html} scope language},
|
||||
{!module: Desugared.Dependency} checks that within
|
||||
a scope, there is no computational circular dependency between the variables
|
||||
of the scope. When the dependency graph is a DAG,
|
||||
{!module: Desugared.Desugared_to_scope} performs a topological ordering to
|
||||
produce an ordered list of the scope definitions compatible with the
|
||||
computation order. All the graph computations are done using the
|
||||
{{:http://ocamlgraph.lri.fr/} Ocamlgraph} library.
|
||||
|
||||
The other important piece of work performed by
|
||||
{!module: Desugared.Desugared_to_scope} is the construction of the default trees
|
||||
(see {!constructor: Dcalc.Ast.EDefault}) from the list of prioritized rules.
|
283
src/catala/desugared/desugared_to_scope.ml
Normal file
@ -0,0 +1,283 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Translation from {!module: Desugared.Ast} to {!module: Scopelang.Ast} *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module Cli = Utils.Cli
|
||||
|
||||
(** {1 Rule tree construction} *)
|
||||
|
||||
type rule_tree = Leaf of Ast.rule | Node of rule_tree list * Ast.rule
|
||||
|
||||
(** Transforms a flat list of rules into a tree, taking into account the priorities declared between
|
||||
rules
|
||||
|
||||
{e Invariant:} there are no exceptions cycles
|
||||
|
||||
{e Invariant:} there are no dandling exception pointers in the rules *)
|
||||
let rec def_map_to_tree (def_info : Ast.ScopeDef.t)
|
||||
(is_def_func : Scopelang.Ast.typ Pos.marked option) (def : Ast.rule Ast.RuleMap.t) :
|
||||
rule_tree list =
|
||||
(* first we look to the rules that don't have any exceptions *)
|
||||
let has_no_exception (r : Ast.RuleName.t) _ =
|
||||
not
|
||||
(Ast.RuleMap.exists
|
||||
(fun _ r' -> match r'.Ast.exception_to_rule with Some r_ex -> r_ex = r | None -> false)
|
||||
def)
|
||||
in
|
||||
let no_exceptions = Ast.RuleMap.filter has_no_exception def in
|
||||
(* Then, for each top-level rule (that has no exceptions), we build a rule tree *)
|
||||
(* Among the top-level rules are the base rules that are exceptions to nothing *)
|
||||
let base_rules, rules_that_are_exceptions =
|
||||
Ast.RuleMap.partition (fun _ r -> Option.is_none r.Ast.exception_to_rule) no_exceptions
|
||||
in
|
||||
let base_trees : rule_tree Ast.RuleMap.t =
|
||||
Ast.RuleMap.map
|
||||
(fun r ->
|
||||
(* we look at the the eventual rule of which r is an exception *)
|
||||
match r.Ast.exception_to_rule with None -> Leaf r | Some _ -> assert false
|
||||
(* should not happen *))
|
||||
base_rules
|
||||
in
|
||||
(* Now let's deal with the rules that are exceptions but have no exception. We have to bucket
|
||||
these, each bucket containing all the rules that are exception to the same rule *)
|
||||
let exception_targets =
|
||||
Ast.RuleMap.fold
|
||||
(fun _ r acc ->
|
||||
match r.Ast.exception_to_rule with
|
||||
| None -> assert false (* should not happen *)
|
||||
| Some r' -> Ast.RuleMap.add r' () acc)
|
||||
rules_that_are_exceptions Ast.RuleMap.empty
|
||||
in
|
||||
(* In each bucket corresponding to an exception target, we have all the rules that are exceptions
|
||||
to the target *)
|
||||
let exception_trees =
|
||||
Ast.RuleMap.mapi
|
||||
(fun r' _ ->
|
||||
(* we recursively call the function of a def where we have removed exception edges: this is
|
||||
why the function should terminate *)
|
||||
let def_rec =
|
||||
Ast.RuleMap.map
|
||||
(fun r ->
|
||||
{
|
||||
r with
|
||||
Ast.exception_to_rule =
|
||||
( match r.Ast.exception_to_rule with
|
||||
| None -> None
|
||||
| Some r'' -> if r'' = r' then None else Some r'' );
|
||||
})
|
||||
def
|
||||
in
|
||||
let def_rec =
|
||||
Ast.RuleMap.filter (fun r _ -> not (Ast.RuleMap.mem r exception_targets)) def_rec
|
||||
in
|
||||
let exceptions = def_map_to_tree def_info is_def_func def_rec in
|
||||
Node (exceptions, Ast.RuleMap.find r' def))
|
||||
exception_targets
|
||||
in
|
||||
List.map snd (Ast.RuleMap.bindings base_trees)
|
||||
@ List.map snd (Ast.RuleMap.bindings exception_trees)
|
||||
|
||||
(** From the {!type: rule_tree}, builds an {!constructor: Dcalc.Ast.EDefault} expression in the
|
||||
scope language. The [~toplevel] parameter is used to know when to place the toplevel binding in
|
||||
the case of functions. *)
|
||||
let rec rule_tree_to_expr ~(toplevel : bool) (def_pos : Pos.t)
|
||||
(is_func : Scopelang.Ast.Var.t option) (tree : rule_tree) :
|
||||
Scopelang.Ast.expr Pos.marked Bindlib.box =
|
||||
let exceptions, rule =
|
||||
match tree with Leaf r -> ([], r) | Node (exceptions, r) -> (exceptions, r)
|
||||
in
|
||||
(* because each rule has its own variable parameter and we want to convert the whole rule tree
|
||||
into a function, we need to perform some alpha-renaming of all the expressions *)
|
||||
let substitute_parameter (e : Scopelang.Ast.expr Pos.marked Bindlib.box) :
|
||||
Scopelang.Ast.expr Pos.marked Bindlib.box =
|
||||
match (is_func, rule.parameter) with
|
||||
| Some new_param, Some (old_param, _) ->
|
||||
let binder = Bindlib.bind_var old_param e in
|
||||
Bindlib.box_apply2
|
||||
(fun binder new_param -> Bindlib.subst binder new_param)
|
||||
binder (Bindlib.box_var new_param)
|
||||
| None, None -> e
|
||||
| _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
let just = substitute_parameter rule.Ast.just in
|
||||
let cons = substitute_parameter rule.Ast.cons in
|
||||
let exceptions =
|
||||
Bindlib.box_list (List.map (rule_tree_to_expr ~toplevel:false def_pos is_func) exceptions)
|
||||
in
|
||||
let default =
|
||||
Bindlib.box_apply3
|
||||
(fun exceptions just cons ->
|
||||
(Scopelang.Ast.EDefault (exceptions, just, cons), Pos.get_position just))
|
||||
exceptions just cons
|
||||
in
|
||||
match (is_func, rule.parameter) with
|
||||
| None, None -> default
|
||||
| Some new_param, Some (_, typ) ->
|
||||
if toplevel then
|
||||
(* When we're creating a function from multiple defaults, we must check that the result
|
||||
returned by the function is not empty *)
|
||||
let default =
|
||||
Bindlib.box_apply
|
||||
(fun (default : Scopelang.Ast.expr * Pos.t) ->
|
||||
( Scopelang.Ast.EApp
|
||||
((Scopelang.Ast.EOp (Dcalc.Ast.Unop Dcalc.Ast.ErrorOnEmpty), def_pos), [ default ]),
|
||||
def_pos ))
|
||||
default
|
||||
in
|
||||
Scopelang.Ast.make_abs (Array.of_list [ new_param ]) default def_pos [ typ ] def_pos
|
||||
else default
|
||||
| _ -> (* should not happen *) assert false
|
||||
|
||||
(** {1 AST translation} *)
|
||||
|
||||
(** Translates a definition inside a scope, the resulting expression should be an {!constructor:
|
||||
Dcalc.Ast.EDefault} *)
|
||||
let translate_def (def_info : Ast.ScopeDef.t) (def : Ast.rule Ast.RuleMap.t)
|
||||
(typ : Scopelang.Ast.typ Pos.marked) (is_cond : bool) : Scopelang.Ast.expr Pos.marked =
|
||||
(* Here, we have to transform this list of rules into a default tree. *)
|
||||
let is_func _ (r : Ast.rule) : bool = Option.is_some r.Ast.parameter in
|
||||
let all_rules_func = Ast.RuleMap.for_all is_func def in
|
||||
let all_rules_not_func = Ast.RuleMap.for_all (fun n r -> not (is_func n r)) def in
|
||||
let is_def_func : Scopelang.Ast.typ Pos.marked option =
|
||||
if all_rules_func && Ast.RuleMap.cardinal def > 0 then
|
||||
match Pos.unmark typ with
|
||||
| Scopelang.Ast.TArrow (t_param, _) -> Some t_param
|
||||
| _ ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The definitions of %a are function but its type, %a, is not a function type"
|
||||
Ast.ScopeDef.format_t def_info Scopelang.Print.format_typ typ)
|
||||
(Pos.get_position typ)
|
||||
else if all_rules_not_func then None
|
||||
else
|
||||
Errors.raise_multispanned_error
|
||||
"some definitions of the same variable are functions while others aren't"
|
||||
( List.map
|
||||
(fun (_, r) ->
|
||||
(Some "This definition is a function:", Pos.get_position (Bindlib.unbox r.Ast.cons)))
|
||||
(Ast.RuleMap.bindings (Ast.RuleMap.filter is_func def))
|
||||
@ List.map
|
||||
(fun (_, r) ->
|
||||
( Some "This definition is not a function:",
|
||||
Pos.get_position (Bindlib.unbox r.Ast.cons) ))
|
||||
(Ast.RuleMap.bindings (Ast.RuleMap.filter (fun n r -> not (is_func n r)) def)) )
|
||||
in
|
||||
let top_list = def_map_to_tree def_info is_def_func def in
|
||||
let top_value =
|
||||
(if is_cond then Ast.always_false_rule else Ast.empty_rule) Pos.no_pos is_def_func
|
||||
in
|
||||
Bindlib.unbox
|
||||
(rule_tree_to_expr ~toplevel:true
|
||||
(Ast.ScopeDef.get_position def_info)
|
||||
(Option.map (fun _ -> Scopelang.Ast.Var.make ("ρ", Pos.no_pos)) is_def_func)
|
||||
( match top_list with
|
||||
| [] ->
|
||||
(* In this case, there are no rules to define the expression *)
|
||||
Leaf top_value
|
||||
| _ -> Node (top_list, top_value) ))
|
||||
|
||||
(** Translates a scope *)
|
||||
let translate_scope (scope : Ast.scope) : Scopelang.Ast.scope_decl =
|
||||
let scope_dependencies = Dependency.build_scope_dependencies scope in
|
||||
Dependency.check_for_cycle scope scope_dependencies;
|
||||
let scope_ordering = Dependency.correct_computation_ordering scope_dependencies in
|
||||
let scope_decl_rules =
|
||||
List.flatten
|
||||
(List.map
|
||||
(fun vertex ->
|
||||
match vertex with
|
||||
| Dependency.Vertex.Var (var : Scopelang.Ast.ScopeVar.t) ->
|
||||
let var_def, var_typ, is_cond =
|
||||
Ast.ScopeDefMap.find (Ast.ScopeDef.Var var) scope.scope_defs
|
||||
in
|
||||
let expr_def = translate_def (Ast.ScopeDef.Var var) var_def var_typ is_cond in
|
||||
[
|
||||
Scopelang.Ast.Definition
|
||||
( ( Scopelang.Ast.ScopeVar
|
||||
(var, Pos.get_position (Scopelang.Ast.ScopeVar.get_info var)),
|
||||
Pos.get_position (Scopelang.Ast.ScopeVar.get_info var) ),
|
||||
var_typ,
|
||||
expr_def );
|
||||
]
|
||||
| Dependency.Vertex.SubScope sub_scope_index ->
|
||||
(* Before calling the sub_scope, we need to include all the re-definitions of
|
||||
subscope parameters*)
|
||||
let sub_scope =
|
||||
Scopelang.Ast.SubScopeMap.find sub_scope_index scope.scope_sub_scopes
|
||||
in
|
||||
let sub_scope_vars_redefs =
|
||||
Ast.ScopeDefMap.mapi
|
||||
(fun def_key (def, def_typ, is_cond) ->
|
||||
match def_key with
|
||||
| Ast.ScopeDef.Var _ -> assert false (* should not happen *)
|
||||
| Ast.ScopeDef.SubScopeVar (_, sub_scope_var) ->
|
||||
let expr_def = translate_def def_key def def_typ is_cond in
|
||||
let subscop_real_name =
|
||||
Scopelang.Ast.SubScopeMap.find sub_scope_index scope.scope_sub_scopes
|
||||
in
|
||||
let var_pos =
|
||||
Pos.get_position (Scopelang.Ast.ScopeVar.get_info sub_scope_var)
|
||||
in
|
||||
Scopelang.Ast.Definition
|
||||
( ( Scopelang.Ast.SubScopeVar
|
||||
( subscop_real_name,
|
||||
(sub_scope_index, var_pos),
|
||||
(sub_scope_var, var_pos) ),
|
||||
var_pos ),
|
||||
def_typ,
|
||||
expr_def ))
|
||||
(Ast.ScopeDefMap.filter
|
||||
(fun def_key _def ->
|
||||
match def_key with
|
||||
| Ast.ScopeDef.Var _ -> false
|
||||
| Ast.ScopeDef.SubScopeVar (sub_scope_index', _) ->
|
||||
sub_scope_index = sub_scope_index')
|
||||
scope.scope_defs)
|
||||
in
|
||||
let sub_scope_vars_redefs =
|
||||
List.map snd (Ast.ScopeDefMap.bindings sub_scope_vars_redefs)
|
||||
in
|
||||
sub_scope_vars_redefs @ [ Scopelang.Ast.Call (sub_scope, sub_scope_index) ])
|
||||
scope_ordering)
|
||||
in
|
||||
(* Then, after having computed all the scopes variables, we add the assertions *)
|
||||
let scope_decl_rules =
|
||||
scope_decl_rules
|
||||
@ List.map (fun e -> Scopelang.Ast.Assertion (Bindlib.unbox e)) scope.Ast.scope_assertions
|
||||
in
|
||||
let scope_sig =
|
||||
Scopelang.Ast.ScopeVarSet.fold
|
||||
(fun var acc ->
|
||||
let _, typ, _ = Ast.ScopeDefMap.find (Ast.ScopeDef.Var var) scope.scope_defs in
|
||||
Scopelang.Ast.ScopeVarMap.add var typ acc)
|
||||
scope.scope_vars Scopelang.Ast.ScopeVarMap.empty
|
||||
in
|
||||
{
|
||||
Scopelang.Ast.scope_decl_name = scope.scope_uid;
|
||||
Scopelang.Ast.scope_decl_rules;
|
||||
Scopelang.Ast.scope_sig;
|
||||
}
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
let translate_program (pgrm : Ast.program) : Scopelang.Ast.program =
|
||||
{
|
||||
Scopelang.Ast.program_scopes = Scopelang.Ast.ScopeMap.map translate_scope pgrm.program_scopes;
|
||||
Scopelang.Ast.program_structs = pgrm.program_structs;
|
||||
Scopelang.Ast.program_enums = pgrm.program_enums;
|
||||
}
|
8
src/catala/desugared/dune
Normal file
@ -0,0 +1,8 @@
|
||||
(library
|
||||
(name desugared)
|
||||
(public_name catala.desugared)
|
||||
(libraries utils dcalc scopelang ocamlgraph))
|
||||
|
||||
(documentation
|
||||
(package catala)
|
||||
(mld_files desugared))
|
@ -12,14 +12,22 @@
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module Cli = Utils.Cli
|
||||
module Errors = Utils.Errors
|
||||
module Pos = Utils.Pos
|
||||
|
||||
(** Entry function for the executable. Returns a negative number in case of error. *)
|
||||
let driver (source_file : string) (debug : bool) (unstyled : bool) (wrap_weaved_output : bool)
|
||||
(pygmentize_loc : string option) (backend : string) (language : string option)
|
||||
let driver (source_file : Pos.input_file) (debug : bool) (unstyled : bool)
|
||||
(wrap_weaved_output : bool) (pygmentize_loc : string option) (backend : string)
|
||||
(language : string option) (max_prec_digits : int option) (trace : bool)
|
||||
(ex_scope : string option) (output_file : string option) : int =
|
||||
try
|
||||
Cli.debug_flag := debug;
|
||||
Cli.style_flag := not unstyled;
|
||||
Cli.trace_flag := trace;
|
||||
Cli.debug_print "Reading files...";
|
||||
(match source_file with FileName _ -> () | Contents c -> Cli.contents := c);
|
||||
(match max_prec_digits with None -> () | Some i -> Cli.max_prec_digits := i);
|
||||
let language =
|
||||
match language with
|
||||
| Some l ->
|
||||
@ -35,15 +43,21 @@ let driver (source_file : string) (debug : bool) (unstyled : bool) (wrap_weaved_
|
||||
if backend = "Makefile" then Cli.Makefile
|
||||
else if backend = "LaTeX" then Cli.Latex
|
||||
else if backend = "HTML" then Cli.Html
|
||||
else if backend = "run" then Cli.Run
|
||||
else if backend = "Interpret" then Cli.Run
|
||||
else
|
||||
Errors.raise_error
|
||||
(Printf.sprintf "The selected backend (%s) is not supported by Catala" backend)
|
||||
in
|
||||
let program = Parser_driver.parse_source_files [ source_file ] language in
|
||||
let program = Surface.Parser_driver.parse_source_file source_file language in
|
||||
match backend with
|
||||
| Cli.Makefile ->
|
||||
let backend_extensions_list = [ ".tex" ] in
|
||||
let source_file =
|
||||
match source_file with
|
||||
| FileName f -> f
|
||||
| Contents _ ->
|
||||
Errors.raise_error "The Makefile backend does not work if the input is not a file"
|
||||
in
|
||||
let output_file =
|
||||
match output_file with
|
||||
| Some f -> f
|
||||
@ -61,72 +75,94 @@ let driver (source_file : string) (debug : bool) (unstyled : bool) (wrap_weaved_
|
||||
0
|
||||
| Cli.Latex | Cli.Html ->
|
||||
let language : Cli.backend_lang = Cli.to_backend_lang language in
|
||||
let source_file =
|
||||
match source_file with
|
||||
| FileName f -> f
|
||||
| Contents _ ->
|
||||
Errors.raise_error
|
||||
"The literate programming backends do not work if the input is not a file"
|
||||
in
|
||||
Cli.debug_print
|
||||
(Printf.sprintf "Weaving literate program into %s"
|
||||
(match backend with Cli.Latex -> "LaTeX" | Cli.Html -> "HTML" | _ -> assert false));
|
||||
( match backend with
|
||||
| Cli.Latex -> "LaTeX"
|
||||
| Cli.Html -> "HTML"
|
||||
| _ -> assert false (* should not happen *) ));
|
||||
let output_file =
|
||||
match output_file with
|
||||
| Some f -> f
|
||||
| None -> (
|
||||
Filename.remove_extension source_file
|
||||
^ match backend with Cli.Latex -> ".tex" | Cli.Html -> ".html" | _ -> assert false )
|
||||
^
|
||||
match backend with Cli.Latex -> ".tex" | Cli.Html -> ".html" | _ -> assert false
|
||||
(* should not happen *) )
|
||||
in
|
||||
let oc = open_out output_file in
|
||||
let weave_output =
|
||||
match backend with
|
||||
| Cli.Latex -> Latex.ast_to_latex language
|
||||
| Cli.Html -> Html.ast_to_html pygmentize_loc language
|
||||
| Cli.Latex -> Literate.Latex.ast_to_latex language
|
||||
| Cli.Html -> Literate.Html.ast_to_html pygmentize_loc language
|
||||
| _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
Cli.debug_print (Printf.sprintf "Writing to %s" output_file);
|
||||
let fmt = Format.formatter_of_out_channel oc in
|
||||
if wrap_weaved_output then
|
||||
match backend with
|
||||
| Cli.Latex ->
|
||||
Latex.wrap_latex program.Catala_ast.program_source_files pygmentize_loc language fmt
|
||||
(fun fmt -> weave_output fmt program)
|
||||
Literate.Latex.wrap_latex program.Surface.Ast.program_source_files pygmentize_loc
|
||||
language fmt (fun fmt -> weave_output fmt program)
|
||||
| Cli.Html ->
|
||||
Html.wrap_html program.Catala_ast.program_source_files pygmentize_loc language fmt
|
||||
(fun fmt -> weave_output fmt program)
|
||||
| _ -> assert false
|
||||
Literate.Html.wrap_html program.Surface.Ast.program_source_files pygmentize_loc
|
||||
language fmt (fun fmt -> weave_output fmt program)
|
||||
| _ -> assert false (* should not happen *)
|
||||
else weave_output fmt program;
|
||||
close_out oc;
|
||||
0
|
||||
| Cli.Run ->
|
||||
let ctxt = Name_resolution.form_context program in
|
||||
Cli.debug_print "Name resolution...";
|
||||
let ctxt = Surface.Name_resolution.form_context program in
|
||||
let scope_uid =
|
||||
match ex_scope with
|
||||
| None -> Errors.raise_error "No scope was provided for execution."
|
||||
| Some name -> (
|
||||
match Uid.IdentMap.find_opt name ctxt.scope_idmap with
|
||||
match Desugared.Ast.IdentMap.find_opt name ctxt.scope_idmap with
|
||||
| None ->
|
||||
Errors.raise_error
|
||||
(Printf.sprintf "There is no scope %s inside the program." name)
|
||||
(Printf.sprintf "There is no scope \"%s\" inside the program." name)
|
||||
| Some uid -> uid )
|
||||
in
|
||||
let prgm = Desugaring.translate_program_to_scope ctxt program in
|
||||
let scope =
|
||||
match Uid.ScopeMap.find_opt scope_uid prgm with
|
||||
| Some scope -> scope
|
||||
| None ->
|
||||
let scope_info = Uid.Scope.get_info scope_uid in
|
||||
Errors.raise_spanned_error
|
||||
(Printf.sprintf
|
||||
"Scope %s does not define anything, and therefore cannot be executed"
|
||||
(Pos.unmark scope_info))
|
||||
(Pos.get_position scope_info)
|
||||
Cli.debug_print "Desugaring...";
|
||||
let prgm = Surface.Desugaring.desugar_program ctxt program in
|
||||
Cli.debug_print "Collecting rules...";
|
||||
let prgm = Desugared.Desugared_to_scope.translate_program prgm in
|
||||
Cli.debug_print "Translating to default calculus...";
|
||||
let prgm = Scopelang.Scope_to_dcalc.translate_program prgm scope_uid in
|
||||
(* Cli.debug_print (Format.asprintf "Output program:@\n%a" Dcalc.Print.format_expr prgm); *)
|
||||
Cli.debug_print "Typechecking...";
|
||||
let _typ = Dcalc.Typing.infer_type prgm in
|
||||
(* Cli.debug_print (Format.asprintf "Typechecking results :@\n%a" Dcalc.Print.format_typ
|
||||
typ); *)
|
||||
Cli.debug_print "Starting interpretation...";
|
||||
let results = Dcalc.Interpreter.interpret_program prgm in
|
||||
let results =
|
||||
List.sort
|
||||
(fun (v1, _) (v2, _) -> String.compare (Bindlib.name_of v1) (Bindlib.name_of v2))
|
||||
results
|
||||
in
|
||||
let exec_ctxt = Scope_interpreter.execute_scope ctxt prgm scope in
|
||||
Lambda_interpreter.ExecContext.iter
|
||||
(fun context_key value ->
|
||||
Cli.result_print
|
||||
(Format.asprintf "Computation successful!%s"
|
||||
(if List.length results > 0 then " Results:" else ""));
|
||||
List.iter
|
||||
(fun (var, result) ->
|
||||
Cli.result_print
|
||||
(Printf.sprintf "%s -> %s"
|
||||
(Lambda_interpreter.ExecContextKey.format_t context_key)
|
||||
(Format_lambda.print_term ((value, Pos.no_pos), TDummy))))
|
||||
exec_ctxt;
|
||||
(Format.asprintf "@[<hov 2>%s@ =@ %a@]" (Bindlib.name_of var) Dcalc.Print.format_expr
|
||||
result))
|
||||
results;
|
||||
0
|
||||
with Errors.StructuredError (msg, pos) ->
|
||||
Cli.error_print (Errors.print_structured_error msg pos);
|
||||
exit (-1)
|
||||
-1
|
||||
|
||||
let main () = Cmdliner.Term.exit @@ Cmdliner.Term.eval (Cli.catala_t driver, Cli.info)
|
||||
let main () =
|
||||
Cmdliner.Term.exit @@ Cmdliner.Term.eval (Cli.catala_t (fun f -> driver (FileName f)), Cli.info)
|
||||
|
@ -1,11 +1,7 @@
|
||||
(include_subdirs unqualified)
|
||||
|
||||
(library
|
||||
(public_name catala)
|
||||
(libraries ANSITerminal sedlex menhirLib re cmdliner dune-build-info
|
||||
ocamlgraph)
|
||||
(preprocess
|
||||
(pps sedlex.ppx)))
|
||||
(libraries catala.utils catala.surface catala.desugared catala.literate
|
||||
catala.dcalc))
|
||||
|
||||
(documentation
|
||||
(package catala))
|
||||
|
73
src/catala/index.mld
Normal file
@ -0,0 +1,73 @@
|
||||
{0 The Catala compiler}
|
||||
|
||||
{1 Architecture}
|
||||
|
||||
The architecture of the Catala compiler is inspired by
|
||||
{{: https://compcert.org/} CompCert} or the {{: https://nanopass.org/} Nanopass}
|
||||
framework, and is structured around many intermediate representations connected
|
||||
by successive translations passes.
|
||||
|
||||
Here is the recap picture of the different intermediate representations of the
|
||||
Catala compiler (made with an {{: https://textik.com/#c1c1fecda5209492} ASCII diagram tool}):
|
||||
|
||||
{v
|
||||
+---------------+
|
||||
| |
|
||||
| Surface AST |
|
||||
| |
|
||||
+---------------+
|
||||
|
|
||||
* Separate code from legislation |
|
||||
* Remove syntactic sugars |
|
||||
v
|
||||
+---------------+
|
||||
| |
|
||||
| Desugared AST |
|
||||
| |
|
||||
+---------------+
|
||||
|
|
||||
* Build rule trees for each definition |
|
||||
* Order variable computations inside scope |
|
||||
v
|
||||
+--------------------+
|
||||
| |
|
||||
| Scope language AST |
|
||||
| |
|
||||
+--------------------+
|
||||
|
|
||||
* Convert scopes into functions |
|
||||
* Thunking of subscope arguments |
|
||||
|
|
||||
v
|
||||
+----------------------+
|
||||
| |
|
||||
| Default calculus AST |
|
||||
| |
|
||||
+----------------------+ v}
|
||||
|
||||
{1 List of top-level modules }
|
||||
|
||||
Each of those intermediate representation is bundled into its own `dune` bundle
|
||||
module. Click on the items below if you want to dive straight into the signatures.
|
||||
|
||||
{!modules: Surface Desugared Scopelang Dcalc }
|
||||
|
||||
More documentation can be found on each intermediate representations here.
|
||||
|
||||
{ul
|
||||
{li {{: surface.html} The surface representation}}
|
||||
{li {{: desugared.html} The desugared representation}}
|
||||
{li {{: scopelang.html} The scope language }}
|
||||
{li {{: dcalc.html} The default calculus}}
|
||||
}
|
||||
|
||||
The main compilation chain is defined in:
|
||||
|
||||
{!modules: Catala.Driver}
|
||||
|
||||
Last, two more modules contain additional features for the compiler:
|
||||
|
||||
{ul
|
||||
{li {{: literate.html} Literate programming}}
|
||||
{li {{: utils.html} Compiler utilities}}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module IdentMap = Map.Make (String)
|
||||
|
||||
(* Printing functions for Lambda_ast.term *)
|
||||
|
||||
let rec format_typ (ty : Lambda_ast.typ) : string =
|
||||
match ty with
|
||||
| TBool -> "bool"
|
||||
| TInt -> "int"
|
||||
| TArrow (t1, t2) -> Format.sprintf "(%s) -> (%s)" (format_typ t1) (format_typ t2)
|
||||
| TDummy -> "??"
|
||||
|
||||
(** Operator printer *)
|
||||
let print_op (op : Lambda_ast.op) : string =
|
||||
match op with
|
||||
| Binop binop -> (
|
||||
match binop with
|
||||
| And -> "and"
|
||||
| Or -> "or"
|
||||
| Add -> "+"
|
||||
| Sub -> "-"
|
||||
| Mult -> "*"
|
||||
| Div -> "/"
|
||||
| Lt -> "<"
|
||||
| Lte -> "<="
|
||||
| Gt -> ">"
|
||||
| Gte -> ">="
|
||||
| Eq -> "="
|
||||
| Neq -> "!=" )
|
||||
| Unop Not -> "not"
|
||||
| Unop Minus -> "-"
|
||||
|
||||
let rec repeat_string n s = if n = 0 then "" else s ^ repeat_string (n - 1) s
|
||||
|
||||
let print_prefix (prefix : Lambda_ast.var_prefix) : string =
|
||||
match prefix with
|
||||
| NoPrefix -> ""
|
||||
| SubScopePrefix s -> Uid.SubScope.format_t s ^ "."
|
||||
| CallerPrefix (i, s) -> (
|
||||
repeat_string i "CALLER."
|
||||
^ match s with None -> "" | Some s -> Uid.SubScope.format_t s ^ "." )
|
||||
|
||||
(** Print Lambda_ast.term *)
|
||||
let rec print_term (((t, _), _) : Lambda_ast.term) : string =
|
||||
match t with
|
||||
| EVar (s, uid) -> Printf.sprintf "%s%s" (print_prefix s) (Uid.Var.format_t uid)
|
||||
| ELocalVar uid -> Uid.LocalVar.format_t uid
|
||||
| EFun (binders, body) ->
|
||||
let sbody = print_term body in
|
||||
Printf.sprintf "fun %s -> %s"
|
||||
(binders |> List.map (fun (uid, _) -> Uid.LocalVar.format_t uid) |> String.concat " ")
|
||||
sbody
|
||||
| EApp (f, args) ->
|
||||
Printf.sprintf "(%s) [%s]" (print_term f) (args |> List.map print_term |> String.concat ";")
|
||||
| EIfThenElse (tif, tthen, telse) ->
|
||||
Printf.sprintf "IF %s THEN %s ELSE %s" (print_term tif) (print_term tthen) (print_term telse)
|
||||
| EInt i -> Printf.sprintf "%d" i
|
||||
| EBool b -> if b then "true" else "false"
|
||||
| EDec (i, f) -> Printf.sprintf "%d.%d" i f
|
||||
| EOp op -> print_op op
|
||||
| EDefault t -> print_default_term t
|
||||
|
||||
and print_default_term (term : Lambda_ast.default_term) : string =
|
||||
( term.defaults
|
||||
|> List.mapi (fun i (cond, body) ->
|
||||
Printf.sprintf "[%d]\t%s => %s" i (print_term cond) (print_term body))
|
||||
|> String.concat "\n" )
|
||||
^ "\n"
|
||||
^ ( term.ordering
|
||||
|> List.map (fun (hi, lo) -> Printf.sprintf "%d > %d" hi lo)
|
||||
|> String.concat ", " )
|
@ -1,120 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(* TDummy means the term is not typed *)
|
||||
type typ = TBool | TInt | TArrow of typ * typ | TDummy
|
||||
|
||||
type literal = Catala_ast.literal
|
||||
|
||||
type binop = Catala_ast.binop
|
||||
|
||||
type unop = Catala_ast.unop
|
||||
|
||||
type op = Binop of binop | Unop of unop
|
||||
|
||||
type binding = Uid.LocalVar.t * typ
|
||||
|
||||
type var_prefix =
|
||||
(* See [Scope_interpreter] for details about the meaning of this case. The `int` means the number
|
||||
of times you have to go to the parent caller to get the variable *)
|
||||
| CallerPrefix of int * Uid.SubScope.t option
|
||||
| NoPrefix
|
||||
| SubScopePrefix of Uid.SubScope.t
|
||||
|
||||
type term = untyped_term Pos.marked * typ
|
||||
|
||||
and untyped_term =
|
||||
| EVar of var_prefix * Uid.Var.t
|
||||
(** This case is only for terms embedded in the scope language *)
|
||||
| ELocalVar of Uid.LocalVar.t
|
||||
| EFun of binding list * term
|
||||
| EApp of term * term list
|
||||
| EIfThenElse of term * term * term
|
||||
| EInt of int
|
||||
| EBool of bool
|
||||
| EDec of int * int
|
||||
| EOp of op
|
||||
| EDefault of default_term
|
||||
|
||||
(* (x,y) in ordering means that default x has precedence over default y : if both are true then x
|
||||
would be choser over y *)
|
||||
and default_term = { defaults : (term * term) list; ordering : (int * int) list }
|
||||
|
||||
let untype (((term, _), _) : term) : untyped_term = term
|
||||
|
||||
let get_pos (((_, pos), _) : term) : Pos.t = pos
|
||||
|
||||
let get_typ ((_, typ) : term) : typ = typ
|
||||
|
||||
let map_untype (f : untyped_term -> untyped_term) (((term, pos), typ) : term) : term =
|
||||
((f term, pos), typ)
|
||||
|
||||
let map_untype2 (f : untyped_term -> untyped_term -> untyped_term) (((t1, pos), typ) : term)
|
||||
(((t2, _), _) : term) : term =
|
||||
((f t1 t2, pos), typ)
|
||||
|
||||
let empty_default_term : default_term = { defaults = []; ordering = [] }
|
||||
|
||||
let add_default (just : term) (cons : term) (term : default_term) =
|
||||
{ term with defaults = term.defaults @ [ (just, cons) ] }
|
||||
|
||||
(** Merge two defalts terms, taking into account that one has higher precedence than the other *)
|
||||
let merge_default_terms (lo_term : default_term) (hi_term : default_term) : default_term =
|
||||
let n = List.length lo_term.defaults in
|
||||
let n' = List.length hi_term.defaults in
|
||||
let defaults = lo_term.defaults @ hi_term.defaults in
|
||||
let rec add_hi_prec = function
|
||||
| [] -> lo_term.ordering
|
||||
| (k, k') :: xs -> (n + k, n + k') :: add_hi_prec xs
|
||||
in
|
||||
let prec = add_hi_prec hi_term.ordering in
|
||||
let gen_prec lo hi =
|
||||
List.fold_left
|
||||
(fun acc x_lo ->
|
||||
let sub_list = List.fold_left (fun acc' x_hi -> (x_hi, x_lo) :: acc') [] hi in
|
||||
sub_list :: acc)
|
||||
[] lo
|
||||
|> List.flatten
|
||||
in
|
||||
let rec gen_list i j acc = if i = j then acc else gen_list (i + 1) j (i :: acc) in
|
||||
let gen_list i j = gen_list i j [] in
|
||||
let prec' = gen_prec (gen_list 0 n) (gen_list n (n + n')) in
|
||||
{ defaults; ordering = prec @ prec' }
|
||||
|
||||
(** Returns the free variables (scope language variables) of a term. Used to build the dependency
|
||||
graph *)
|
||||
let rec term_fv (term : term) : Uid.ScopeDefSet.t =
|
||||
match untype term with
|
||||
| EVar (NoPrefix, uid) -> Uid.ScopeDefSet.singleton (Uid.ScopeDef.Var uid)
|
||||
| EVar (SubScopePrefix sub_uid, uid) ->
|
||||
Uid.ScopeDefSet.singleton (Uid.ScopeDef.SubScopeVar (sub_uid, uid))
|
||||
| EVar (CallerPrefix _, _) ->
|
||||
Uid.ScopeDefSet.empty
|
||||
(* here we return an empty dependency because when calling a subscope, the variables of the
|
||||
caller graph needed for it are already computed *)
|
||||
| ELocalVar _ -> Uid.ScopeDefSet.empty
|
||||
| EFun (_, body) -> term_fv body
|
||||
| EApp (f, args) ->
|
||||
List.fold_left (fun fv arg -> Uid.ScopeDefSet.union fv (term_fv arg)) (term_fv f) args
|
||||
| EIfThenElse (t_if, t_then, t_else) ->
|
||||
Uid.ScopeDefSet.union (term_fv t_if) (Uid.ScopeDefSet.union (term_fv t_then) (term_fv t_else))
|
||||
| EDefault default -> default_term_fv default
|
||||
| EBool _ | EInt _ | EDec _ | EOp _ -> Uid.ScopeDefSet.empty
|
||||
|
||||
and default_term_fv (term : default_term) : Uid.ScopeDefSet.t =
|
||||
List.fold_left
|
||||
(fun acc (cond, body) ->
|
||||
let fv = Uid.ScopeDefSet.union (term_fv cond) (term_fv body) in
|
||||
Uid.ScopeDefSet.union fv acc)
|
||||
Uid.ScopeDefSet.empty term.defaults
|
@ -1,218 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
open Lambda_ast
|
||||
|
||||
type uid = int
|
||||
|
||||
type scope_uid = int
|
||||
|
||||
module ExecContextKey = struct
|
||||
type t = LocalVar of Uid.LocalVar.t | ScopeVar of var_prefix * Uid.Var.t
|
||||
|
||||
let compare x y =
|
||||
match (x, y) with
|
||||
| LocalVar x, LocalVar y -> Uid.LocalVar.compare x y
|
||||
| ScopeVar (x1, x2), ScopeVar (y1, y2) -> (
|
||||
match (x1, y1) with
|
||||
| NoPrefix, NoPrefix | CallerPrefix _, CallerPrefix _ -> Uid.Var.compare x2 y2
|
||||
| SubScopePrefix x1, SubScopePrefix y1 ->
|
||||
let sub_comp = Uid.SubScope.compare x1 y1 in
|
||||
if sub_comp = 0 then Uid.Var.compare x2 y2 else sub_comp
|
||||
| _ -> compare x y )
|
||||
| _ -> compare x y
|
||||
|
||||
let format_t (x : t) : string =
|
||||
match x with
|
||||
| LocalVar x -> Uid.LocalVar.format_t x
|
||||
| ScopeVar (prefix, var) -> Format_lambda.print_prefix prefix ^ Uid.Var.format_t var
|
||||
end
|
||||
|
||||
module ExecContext = Map.Make (ExecContextKey)
|
||||
|
||||
type exec_context = Lambda_ast.untyped_term ExecContext.t
|
||||
|
||||
let format_exec_context (ctx : exec_context) =
|
||||
String.concat "\n"
|
||||
(List.map
|
||||
(fun (key, value) ->
|
||||
Printf.sprintf "%s -> %s" (ExecContextKey.format_t key)
|
||||
(Format_lambda.print_term ((value, Pos.no_pos), TDummy)))
|
||||
(ExecContext.bindings ctx))
|
||||
|
||||
let empty_exec_ctxt = ExecContext.empty
|
||||
|
||||
let raise_default_conflict (def : Uid.ScopeDef.t) (true_pos : Pos.t list) (false_pos : Pos.t list) =
|
||||
let var_str = Uid.ScopeDef.format_t def in
|
||||
let var_pos =
|
||||
match def with
|
||||
| Uid.ScopeDef.SubScopeVar (_, v) | Uid.ScopeDef.Var v -> Pos.get_position (Uid.Var.get_info v)
|
||||
in
|
||||
|
||||
if List.length true_pos = 0 then
|
||||
let justifications : (string option * Pos.t) list =
|
||||
List.map (fun pos -> (Some "This justification is false:", pos)) false_pos
|
||||
in
|
||||
Errors.raise_multispanned_error
|
||||
(Printf.sprintf "Default logic error for variable %s: no justification is true." var_str)
|
||||
( (Some (Printf.sprintf "The error concerns this variable %s" var_str), var_pos)
|
||||
:: justifications )
|
||||
else
|
||||
let justifications : (string option * Pos.t) list =
|
||||
List.map (fun pos -> (Some "This justification is true:", pos)) true_pos
|
||||
in
|
||||
Errors.raise_multispanned_error
|
||||
"Default logic conflict, multiple justifications are true but are not related by a precedence"
|
||||
( (Some (Printf.sprintf "The conflict concerns this variable %s" var_str), var_pos)
|
||||
:: justifications )
|
||||
|
||||
let rec eval_term (top_uid : Uid.ScopeDef.t) (exec_ctxt : exec_context) (term : Lambda_ast.term) :
|
||||
Lambda_ast.term =
|
||||
let (term, pos), typ = term in
|
||||
let evaled_term =
|
||||
match term with
|
||||
| EFun _ | EInt _ | EDec _ | EBool _ | EOp _ -> term (* already a value *)
|
||||
| ELocalVar uid -> (
|
||||
let ctxt_key = ExecContextKey.LocalVar uid in
|
||||
match ExecContext.find_opt ctxt_key exec_ctxt with
|
||||
| Some t -> t
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Printf.sprintf "Local Variable %s is not defined" (Uid.LocalVar.format_t uid))
|
||||
pos )
|
||||
| EVar (prefix, uid) -> (
|
||||
let ctxt_key = ExecContextKey.ScopeVar (prefix, uid) in
|
||||
match ExecContext.find_opt ctxt_key exec_ctxt with
|
||||
| Some t -> t
|
||||
| None ->
|
||||
Errors.raise_spanned_error
|
||||
(Printf.sprintf "Variable %s is not defined" (Uid.Var.format_t uid))
|
||||
pos )
|
||||
| EApp (f, args) -> (
|
||||
(* First evaluate and match the function body *)
|
||||
let f = f |> eval_term top_uid exec_ctxt |> Lambda_ast.untype in
|
||||
match f with
|
||||
| EFun (bindings, body) ->
|
||||
let exec_ctxt =
|
||||
List.fold_left2
|
||||
(fun ctxt arg (uid, _) ->
|
||||
ExecContext.add (ExecContextKey.LocalVar uid)
|
||||
(arg |> eval_term top_uid exec_ctxt |> Lambda_ast.untype)
|
||||
ctxt)
|
||||
exec_ctxt args bindings
|
||||
in
|
||||
eval_term top_uid exec_ctxt body |> Lambda_ast.untype
|
||||
| EOp op -> (
|
||||
let args =
|
||||
List.map (fun arg -> arg |> eval_term top_uid exec_ctxt |> Lambda_ast.untype) args
|
||||
in
|
||||
match op with
|
||||
| Binop binop -> (
|
||||
match binop with
|
||||
| And | Or ->
|
||||
let b1, b2 =
|
||||
match args with [ EBool b1; EBool b2 ] -> (b1, b2) | _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
EBool (if binop = And then b1 && b2 else b1 || b2)
|
||||
| _ -> (
|
||||
let i1, i2 =
|
||||
match args with [ EInt i1; EInt i2 ] -> (i1, i2) | _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
match binop with
|
||||
| Add | Sub | Mult | Div ->
|
||||
let op_arith =
|
||||
match binop with
|
||||
| Add -> ( + )
|
||||
| Sub -> ( - )
|
||||
| Mult -> ( * )
|
||||
| Div -> ( / )
|
||||
| _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
EInt (op_arith i1 i2)
|
||||
| _ ->
|
||||
let op_comp =
|
||||
match binop with
|
||||
| Lt -> ( < )
|
||||
| Lte -> ( <= )
|
||||
| Gt -> ( > )
|
||||
| Gte -> ( >= )
|
||||
| Eq -> ( = )
|
||||
| Neq -> ( <> )
|
||||
| _ -> assert false
|
||||
(* should not happen *)
|
||||
in
|
||||
EBool (op_comp i1 i2) ) )
|
||||
| Unop Minus -> (
|
||||
match args with
|
||||
| [ EInt i ] -> EInt (-i)
|
||||
| _ -> assert false (* should not happen *) )
|
||||
| Unop Not -> (
|
||||
match args with
|
||||
| [ EBool b ] -> EBool (not b)
|
||||
| _ -> assert false (* should not happen *) ) )
|
||||
| _ -> assert false )
|
||||
| EIfThenElse (t_if, t_then, t_else) ->
|
||||
( match eval_term top_uid exec_ctxt t_if |> Lambda_ast.untype with
|
||||
| EBool b ->
|
||||
if b then eval_term top_uid exec_ctxt t_then else eval_term top_uid exec_ctxt t_else
|
||||
| _ -> assert false (* should not happen *) )
|
||||
|> Lambda_ast.untype
|
||||
| EDefault t -> (
|
||||
match eval_default_term top_uid exec_ctxt t with
|
||||
| Ok value -> value |> Lambda_ast.untype
|
||||
| Error (true_pos, false_pos) -> raise_default_conflict top_uid true_pos false_pos )
|
||||
in
|
||||
((evaled_term, pos), typ)
|
||||
|
||||
(* Evaluates a default term : see the formalization for an insight about this operation *)
|
||||
and eval_default_term (top_uid : Uid.ScopeDef.t) (exec_ctxt : exec_context)
|
||||
(term : Lambda_ast.default_term) : (Lambda_ast.term, Pos.t list * Pos.t list) result =
|
||||
(* First filter out the term which justification are false *)
|
||||
let defaults_numbered : (int * (term * term)) list =
|
||||
List.mapi (fun (x : int) (y : term * term) -> (x, y)) term.defaults
|
||||
in
|
||||
let candidates : 'a list =
|
||||
List.filter
|
||||
(fun (_, (cond, _)) ->
|
||||
match eval_term top_uid exec_ctxt cond |> Lambda_ast.untype with
|
||||
| EBool b -> b
|
||||
| _ -> assert false
|
||||
(* should not happen *))
|
||||
defaults_numbered
|
||||
in
|
||||
(* Now filter out the terms that have a predecessor which justification is true *)
|
||||
let module ISet = Set.Make (Int) in
|
||||
let key_candidates = List.fold_left (fun acc (x, _) -> ISet.add x acc) ISet.empty candidates in
|
||||
let chosen_one =
|
||||
List.fold_left
|
||||
(fun set (lo, hi) -> if ISet.mem lo set && ISet.mem hi set then ISet.remove hi set else set)
|
||||
key_candidates term.ordering
|
||||
in
|
||||
match ISet.elements chosen_one with
|
||||
| [ x ] ->
|
||||
let _, (_, cons) = List.find (fun (i, _) -> i = x) defaults_numbered in
|
||||
Ok (eval_term top_uid exec_ctxt cons)
|
||||
| xs ->
|
||||
let true_pos =
|
||||
xs
|
||||
|> List.map (fun x ->
|
||||
List.find (fun (i, _) -> i = x) defaults_numbered |> snd |> fst |> Lambda_ast.get_pos)
|
||||
in
|
||||
let false_pos : Pos.t list =
|
||||
List.map (fun (_, (cond, _)) -> Lambda_ast.get_pos cond) defaults_numbered
|
||||
in
|
||||
Error (true_pos, false_pos)
|
@ -1,138 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Checks that a term is well typed and annotate it *)
|
||||
let rec type_term (ctxt : Name_resolution.context) (local_ctx : Lambda_ast.typ Uid.LocalVarMap.t)
|
||||
(((t, pos), _) : Lambda_ast.term) : Lambda_ast.term =
|
||||
match t with
|
||||
| EVar (s, uid) ->
|
||||
(* so here we can ignore the subscope uid because the uid of the variable already corresponds
|
||||
to the uid of the variable in its original scope *)
|
||||
let typ = Name_resolution.get_var_typ ctxt uid in
|
||||
((EVar (s, uid), pos), typ)
|
||||
| ELocalVar uid ->
|
||||
let typ = Uid.LocalVarMap.find uid local_ctx in
|
||||
((ELocalVar uid, pos), typ)
|
||||
| EFun (bindings, body) ->
|
||||
let local_ctx =
|
||||
List.fold_left
|
||||
(fun local_ctx (binding, ty) -> Uid.LocalVarMap.add binding ty local_ctx)
|
||||
local_ctx bindings
|
||||
in
|
||||
let body = type_term ctxt local_ctx body in
|
||||
let ret_typ = Lambda_ast.get_typ body in
|
||||
let rec build_typ = function
|
||||
| [] -> ret_typ
|
||||
| (_, arg_t) :: args -> TArrow (arg_t, build_typ args)
|
||||
in
|
||||
let fun_typ = build_typ bindings in
|
||||
((EFun (bindings, body), pos), fun_typ)
|
||||
| EApp (f, args) ->
|
||||
let f = type_term ctxt local_ctx f in
|
||||
let f_typ = Lambda_ast.get_typ f in
|
||||
let args = List.map (type_term ctxt local_ctx) args in
|
||||
let args_typ =
|
||||
List.map (fun arg -> (Lambda_ast.get_typ arg, Pos.get_position (fst arg))) args
|
||||
in
|
||||
let rec check_arrow_typ f_typ args_typ =
|
||||
match (f_typ, args_typ) with
|
||||
| typ, [] -> typ
|
||||
| Lambda_ast.TArrow (arg_typ, ret_typ), fst_typ :: typs ->
|
||||
let fst_typ_s = Pos.unmark fst_typ in
|
||||
if arg_typ = fst_typ_s then check_arrow_typ ret_typ typs
|
||||
else
|
||||
Errors.raise_multispanned_error "error when comparing types of function arguments"
|
||||
[
|
||||
( Some (Printf.sprintf "expected type %s" (Format_lambda.format_typ f_typ)),
|
||||
Pos.get_position (fst f) );
|
||||
( Some (Printf.sprintf "got type %s" (Format_lambda.format_typ fst_typ_s)),
|
||||
Pos.get_position fst_typ );
|
||||
]
|
||||
| _ ->
|
||||
Errors.raise_multispanned_error "wrong number of arguments for function call"
|
||||
[
|
||||
( Some (Printf.sprintf "expected type %s" (Format_lambda.format_typ f_typ)),
|
||||
Pos.get_position (fst f) );
|
||||
( Some
|
||||
(Printf.sprintf "got type %s"
|
||||
(String.concat " -> "
|
||||
(List.map (fun (ty, _) -> Format_lambda.format_typ ty) args_typ))),
|
||||
Pos.get_position (List.hd args_typ) );
|
||||
]
|
||||
in
|
||||
let ret_typ = check_arrow_typ f_typ args_typ in
|
||||
((EApp (f, args), pos), ret_typ)
|
||||
| EIfThenElse (t_if, t_then, t_else) ->
|
||||
let t_if = type_term ctxt local_ctx t_if in
|
||||
let typ_if = Lambda_ast.get_typ t_if in
|
||||
let t_then = type_term ctxt local_ctx t_then in
|
||||
let typ_then = Lambda_ast.get_typ t_then in
|
||||
let t_else = type_term ctxt local_ctx t_else in
|
||||
let typ_else = Lambda_ast.get_typ t_else in
|
||||
if typ_if <> TBool then
|
||||
Errors.raise_spanned_error
|
||||
(Format.sprintf "expecting type bool, got type %s" (Format_lambda.format_typ typ_if))
|
||||
(Pos.get_position (fst t_if))
|
||||
else if typ_then <> typ_else then
|
||||
Errors.raise_multispanned_error
|
||||
"expecting same types for the true and false branches of the conditional"
|
||||
[
|
||||
( Some (Format.sprintf "the true branch has type %s" (Format_lambda.format_typ typ_then)),
|
||||
Pos.get_position (fst t_then) );
|
||||
( Some
|
||||
(Format.sprintf "the false branch has type %s" (Format_lambda.format_typ typ_else)),
|
||||
Pos.get_position (fst t_else) );
|
||||
]
|
||||
else ((EIfThenElse (t_if, t_then, t_else), pos), typ_then)
|
||||
| EInt _ | EDec _ -> ((t, pos), TInt)
|
||||
| EBool _ -> ((t, pos), TBool)
|
||||
| EOp op ->
|
||||
let typ =
|
||||
match op with
|
||||
| Binop binop -> (
|
||||
match binop with
|
||||
| And | Or -> Lambda_ast.TArrow (TBool, TArrow (TBool, TBool))
|
||||
| Add | Sub | Mult | Div -> TArrow (TInt, TArrow (TInt, TInt))
|
||||
| Lt | Lte | Gt | Gte | Eq | Neq -> TArrow (TInt, TArrow (TInt, TBool)) )
|
||||
| Unop Minus -> TArrow (TInt, TInt)
|
||||
| Unop Not -> TArrow (TBool, TBool)
|
||||
in
|
||||
((t, pos), typ)
|
||||
| EDefault t ->
|
||||
let defaults =
|
||||
List.map
|
||||
(fun (just, cons) ->
|
||||
let just_t = type_term ctxt local_ctx just in
|
||||
if Lambda_ast.get_typ just_t <> TBool then
|
||||
let cons = type_term ctxt local_ctx cons in
|
||||
(just_t, cons)
|
||||
else
|
||||
Errors.raise_spanned_error
|
||||
(Format.sprintf "expected type of default condition to be bool, got %s"
|
||||
(Format_lambda.format_typ (Lambda_ast.get_typ just)))
|
||||
(Pos.get_position (fst just)))
|
||||
t.defaults
|
||||
in
|
||||
let typ_cons = List.hd defaults |> snd |> snd in
|
||||
List.iter
|
||||
(fun (_, cons) ->
|
||||
if Lambda_ast.get_typ cons <> typ_cons then
|
||||
Errors.raise_spanned_error
|
||||
(Format.sprintf "expected default condition to be of type %s, got type %s"
|
||||
(Format_lambda.format_typ (Lambda_ast.get_typ cons))
|
||||
(Format_lambda.format_typ typ_cons))
|
||||
(Pos.get_position (fst cons))
|
||||
else ())
|
||||
defaults;
|
||||
((EDefault { t with defaults }, pos), typ_cons)
|
8
src/catala/literate_programming/dune
Normal file
@ -0,0 +1,8 @@
|
||||
(library
|
||||
(name literate)
|
||||
(public_name catala.literate)
|
||||
(libraries re utils surface))
|
||||
|
||||
(documentation
|
||||
(package catala)
|
||||
(mld_files literate))
|
@ -15,18 +15,32 @@
|
||||
(** This modules weaves the source code and the legislative text together into a document that law
|
||||
professionals can understand. *)
|
||||
|
||||
module A = Catala_ast
|
||||
module Pos = Utils.Pos
|
||||
module Cli = Utils.Cli
|
||||
module Errors = Utils.Errors
|
||||
module A = Surface.Ast
|
||||
module P = Printf
|
||||
module R = Re.Pcre
|
||||
module C = Cli
|
||||
|
||||
let pre_html (s : string) = s
|
||||
(** {1 Helpers} *)
|
||||
|
||||
(** Converts double lines into HTML newlines. *)
|
||||
let pre_html (s : string) =
|
||||
let s = String.trim s in
|
||||
let doublenewline = R.regexp "\n\n" in
|
||||
let s = R.substitute ~rex:doublenewline ~subst:(fun _ -> "<br/>\n") s in
|
||||
s
|
||||
|
||||
(** Raise an error if pygments cannot be found *)
|
||||
let raise_failed_pygments (command : string) (error_code : int) : 'a =
|
||||
Errors.raise_error
|
||||
(Printf.sprintf "Weaving to HTML failed: pygmentize command \"%s\" returned with error code %d"
|
||||
command error_code)
|
||||
|
||||
(** Usage: [wrap_html source_files custom_pygments language fmt wrapped]
|
||||
|
||||
Prints an HTML complete page structure around the [wrapped] content. *)
|
||||
let wrap_html (source_files : string list) (custom_pygments : string option)
|
||||
(language : Cli.backend_lang) (fmt : Format.formatter) (wrapped : Format.formatter -> unit) :
|
||||
unit =
|
||||
@ -62,9 +76,7 @@ let wrap_html (source_files : string list) (custom_pygments : string option)
|
||||
| `Fr -> "Implémentation de texte législatif"
|
||||
| `En -> "Legislative text implementation" )
|
||||
(match language with `Fr -> "Document généré par" | `En -> "Document generated by")
|
||||
( match Build_info.V1.version () with
|
||||
| None -> "n/a"
|
||||
| Some v -> Build_info.V1.Version.to_string v )
|
||||
Utils.Cli.version
|
||||
( match language with
|
||||
| `Fr -> "Fichiers sources tissés dans ce document"
|
||||
| `En -> "Source files weaved in this document" )
|
||||
@ -84,6 +96,7 @@ let wrap_html (source_files : string list) (custom_pygments : string option)
|
||||
source_files));
|
||||
wrapped fmt
|
||||
|
||||
(** Performs syntax highlighting on a piece of code by using Pygments and the special Catala lexer. *)
|
||||
let pygmentize_code (c : string Pos.marked) (language : C.backend_lang)
|
||||
(custom_pygments : string option) : string =
|
||||
C.debug_print (Printf.sprintf "Pygmenting the code chunk %s" (Pos.to_string (Pos.get_position c)));
|
||||
@ -118,12 +131,14 @@ let pygmentize_code (c : string Pos.marked) (language : C.backend_lang)
|
||||
close_in oc;
|
||||
output
|
||||
|
||||
type program_state = InsideArticle | OutsideArticle
|
||||
(** {1 Weaving} *)
|
||||
|
||||
let law_article_item_to_html (custom_pygments : string option) (language : C.backend_lang)
|
||||
(fmt : Format.formatter) (i : A.law_article_item) : unit =
|
||||
match i with
|
||||
| A.LawText t -> Format.fprintf fmt "<p class='law-text'>%s</p>" (pre_html t)
|
||||
| A.LawText t ->
|
||||
let t = pre_html t in
|
||||
if t = "" then () else Format.fprintf fmt "<p class='law-text'>%s</p>" t
|
||||
| A.CodeBlock (_, c) ->
|
||||
let date = "\\d\\d/\\d\\d/\\d\\d\\d\\d" in
|
||||
let syms = R.regexp (date ^ "|!=|<=|>=|--|->|\\*|\\/") in
|
||||
@ -141,7 +156,6 @@ let law_article_item_to_html (custom_pygments : string option) (language : C.bac
|
||||
Format.fprintf fmt "<div class='code-wrapper'>\n<div class='filename'>%s</div>\n%s\n</div>"
|
||||
(Pos.get_file (Pos.get_position c))
|
||||
(pygmentize_code (Pos.same_pos_as ("/*" ^ pprinted_c ^ "*/") c) language custom_pygments)
|
||||
| A.LawInclude _ -> ()
|
||||
|
||||
let rec law_structure_to_html (custom_pygments : string option) (language : C.backend_lang)
|
||||
(fmt : Format.formatter) (i : A.law_structure) : unit =
|
||||
@ -155,6 +169,7 @@ let rec law_structure_to_html (custom_pygments : string option) (language : C.ba
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "\n")
|
||||
(law_structure_to_html custom_pygments language)
|
||||
fmt children
|
||||
| A.LawInclude _ -> ()
|
||||
| A.LawArticle (a, children) ->
|
||||
Format.fprintf fmt
|
||||
"<div class='article-container'>\n\n<div class='article-title'><a href='%s'>%s</a></div>\n"
|
||||
@ -172,12 +187,16 @@ let rec law_structure_to_html (custom_pygments : string option) (language : C.ba
|
||||
Format.fprintf fmt "\n</div>"
|
||||
| A.MetadataBlock (b, c) ->
|
||||
law_article_item_to_html custom_pygments language fmt (A.CodeBlock (b, c))
|
||||
| A.IntermediateText t -> Format.fprintf fmt "<p class='law-text'>%s</p>" (pre_html t)
|
||||
| A.IntermediateText t ->
|
||||
let t = pre_html t in
|
||||
if t = "" then () else Format.fprintf fmt "<p class='law-text'>%s</p>" t
|
||||
|
||||
let program_item_to_html (custom_pygments : string option) (language : C.backend_lang)
|
||||
(fmt : Format.formatter) (i : A.program_item) : unit =
|
||||
match i with A.LawStructure s -> law_structure_to_html custom_pygments language fmt s
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
let ast_to_html (custom_pygments : string option) (language : C.backend_lang)
|
||||
(fmt : Format.formatter) (program : A.program) : unit =
|
||||
Format.pp_print_list
|
||||
|
@ -15,10 +15,16 @@
|
||||
(** This modules weaves the source code and the legislative text together into a document that law
|
||||
professionals can understand. *)
|
||||
|
||||
module A = Catala_ast
|
||||
module Pos = Utils.Pos
|
||||
module Cli = Utils.Cli
|
||||
module Errors = Utils.Errors
|
||||
module A = Surface.Ast
|
||||
module R = Re.Pcre
|
||||
module C = Cli
|
||||
|
||||
(** {1 Helpers} *)
|
||||
|
||||
(** Espaces various LaTeX-sensitive characters *)
|
||||
let pre_latexify (s : string) =
|
||||
let percent = R.regexp "%" in
|
||||
let s = R.substitute ~rex:percent ~subst:(fun _ -> "\\%") s in
|
||||
@ -30,6 +36,9 @@ let pre_latexify (s : string) =
|
||||
let s = R.substitute ~rex:underscore ~subst:(fun _ -> "\\_") s in
|
||||
s
|
||||
|
||||
(** Usage: [wrap_latex source_files custom_pygments language fmt wrapped]
|
||||
|
||||
Prints an LaTeX complete documùent structure around the [wrapped] content. *)
|
||||
let wrap_latex (source_files : string list) (custom_pygments : string option)
|
||||
(language : C.backend_lang) (fmt : Format.formatter) (wrapped : Format.formatter -> unit) =
|
||||
Format.fprintf fmt
|
||||
@ -54,7 +63,6 @@ let wrap_latex (source_files : string list) (custom_pygments : string option)
|
||||
\\newunicodechar{→}{$\\rightarrow$}\n\
|
||||
\\newunicodechar{≠}{$\\neq$}\n\n\
|
||||
\\fvset{\n\
|
||||
commandchars=\\\\\\{\\},\n\
|
||||
numbers=left,\n\
|
||||
frame=lines,\n\
|
||||
framesep=3mm,\n\
|
||||
@ -81,9 +89,7 @@ let wrap_latex (source_files : string list) (custom_pygments : string option)
|
||||
| `Fr -> "Implémentation de texte législatif"
|
||||
| `En -> "Legislative text implementation" )
|
||||
(match language with `Fr -> "Document généré par" | `En -> "Document generated by")
|
||||
( match Build_info.V1.version () with
|
||||
| None -> "n/a"
|
||||
| Some v -> Build_info.V1.Version.to_string v )
|
||||
Utils.Cli.version
|
||||
( match language with
|
||||
| `Fr -> "Fichiers sources tissés dans ce document"
|
||||
| `En -> "Source files weaved in this document" )
|
||||
@ -104,6 +110,7 @@ let wrap_latex (source_files : string list) (custom_pygments : string option)
|
||||
wrapped fmt;
|
||||
Format.fprintf fmt "\n\n\\end{document}"
|
||||
|
||||
(** Replaces math operators by their nice unicode counterparts *)
|
||||
let math_syms_replace (c : string) : string =
|
||||
let date = "\\d\\d/\\d\\d/\\d\\d\\d\\d" in
|
||||
let syms = R.regexp (date ^ "|!=|<=|>=|--|->|\\*|/") in
|
||||
@ -119,6 +126,8 @@ let math_syms_replace (c : string) : string =
|
||||
in
|
||||
R.substitute ~rex:syms ~subst:syms2cmd c
|
||||
|
||||
(** {1 Weaving} *)
|
||||
|
||||
let law_article_item_to_latex (language : C.backend_lang) (fmt : Format.formatter)
|
||||
(i : A.law_article_item) : unit =
|
||||
match i with
|
||||
@ -129,18 +138,9 @@ let law_article_item_to_latex (language : C.backend_lang) (fmt : Format.formatte
|
||||
/*%s*/\n\
|
||||
\\end{minted}"
|
||||
(pre_latexify (Filename.basename (Pos.get_file (Pos.get_position c))))
|
||||
(Pos.get_start_line (Pos.get_position c))
|
||||
(Pos.get_start_line (Pos.get_position c) - 1)
|
||||
(match language with `Fr -> "catala_fr" | `En -> "catala_en")
|
||||
(math_syms_replace (Pos.unmark c))
|
||||
| A.LawInclude (A.PdfFile ((file, _), page)) ->
|
||||
let label = file ^ match page with None -> "" | Some p -> Format.sprintf "_page_%d," p in
|
||||
Format.fprintf fmt
|
||||
"\\begin{center}\\textit{Annexe incluse, retranscrite page \\pageref{%s}}\\end{center} \
|
||||
\\begin{figure}[p]\\begin{center}\\includegraphics[%swidth=\\textwidth]{%s}\\label{%s}\\end{center}\\end{figure}"
|
||||
label
|
||||
(match page with None -> "" | Some p -> Format.sprintf "page=%d," p)
|
||||
file label
|
||||
| A.LawInclude (A.CatalaFile _ | A.LegislativeText _) -> ()
|
||||
|
||||
let rec law_structure_to_latex (language : C.backend_lang) (fmt : Format.formatter)
|
||||
(i : A.law_structure) : unit =
|
||||
@ -157,6 +157,15 @@ let rec law_structure_to_latex (language : C.backend_lang) (fmt : Format.formatt
|
||||
Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "\n\n")
|
||||
(law_structure_to_latex language) fmt children
|
||||
| A.LawInclude (A.PdfFile ((file, _), page)) ->
|
||||
let label = file ^ match page with None -> "" | Some p -> Format.sprintf "_page_%d," p in
|
||||
Format.fprintf fmt
|
||||
"\\begin{center}\\textit{Annexe incluse, retranscrite page \\pageref{%s}}\\end{center} \
|
||||
\\begin{figure}[p]\\begin{center}\\includegraphics[%swidth=\\textwidth]{%s}\\label{%s}\\end{center}\\end{figure}"
|
||||
label
|
||||
(match page with None -> "" | Some p -> Format.sprintf "page=%d," p)
|
||||
file label
|
||||
| A.LawInclude (A.CatalaFile _ | A.LegislativeText _) -> ()
|
||||
| A.LawArticle (article, children) ->
|
||||
Format.fprintf fmt "\\paragraph{%s}\n\n" (pre_latexify (Pos.unmark article.law_article_name));
|
||||
Format.pp_print_list
|
||||
@ -174,7 +183,7 @@ let rec law_structure_to_latex (language : C.backend_lang) (fmt : Format.formatt
|
||||
\\end{minted}\n\
|
||||
\\end{tcolorbox}"
|
||||
metadata_title metadata_title
|
||||
(Pos.get_start_line (Pos.get_position c))
|
||||
(Pos.get_start_line (Pos.get_position c) - 1)
|
||||
(pre_latexify (Filename.basename (Pos.get_file (Pos.get_position c))))
|
||||
(match language with `Fr -> "catala_fr" | `En -> "catala_en")
|
||||
(math_syms_replace (Pos.unmark c))
|
||||
@ -184,6 +193,8 @@ let program_item_to_latex (language : C.backend_lang) (fmt : Format.formatter) (
|
||||
: unit =
|
||||
match i with A.LawStructure law_s -> law_structure_to_latex language fmt law_s
|
||||
|
||||
(** {1 API} *)
|
||||
|
||||
let ast_to_latex (language : C.backend_lang) (fmt : Format.formatter) (program : A.program) : unit =
|
||||
Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "\n\n")
|
||||
|
9
src/catala/literate_programming/literate.mld
Normal file
@ -0,0 +1,9 @@
|
||||
{0 Literate programming}
|
||||
|
||||
Related modules:
|
||||
|
||||
{!modules: Literate.Html Literate.Latex}
|
||||
|
||||
These module take the {{:surface.html} surface representation} of the Catala
|
||||
program and process it into different literate programming outputs, like
|
||||
an HTML page or a LaTeX document.
|
@ -1,152 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
type t = Lexing.position * Lexing.position
|
||||
|
||||
let from_info (file : string) (sline : int) (scol : int) (eline : int) (ecol : int) : t =
|
||||
let spos =
|
||||
{ Lexing.pos_fname = file; Lexing.pos_lnum = sline; Lexing.pos_cnum = scol; Lexing.pos_bol = 1 }
|
||||
in
|
||||
let epos =
|
||||
{ Lexing.pos_fname = file; Lexing.pos_lnum = eline; Lexing.pos_cnum = ecol; Lexing.pos_bol = 1 }
|
||||
in
|
||||
(spos, epos)
|
||||
|
||||
let get_start_line (pos : t) : int =
|
||||
let s, _ = pos in
|
||||
s.Lexing.pos_lnum
|
||||
|
||||
let get_start_column (pos : t) : int =
|
||||
let s, _ = pos in
|
||||
s.Lexing.pos_cnum - s.Lexing.pos_bol + 1
|
||||
|
||||
let get_end_line (pos : t) : int =
|
||||
let _, e = pos in
|
||||
e.Lexing.pos_lnum
|
||||
|
||||
let get_end_column (pos : t) : int =
|
||||
let _, e = pos in
|
||||
e.Lexing.pos_cnum - e.Lexing.pos_bol + 1
|
||||
|
||||
let get_file (pos : t) : string = (fst pos).Lexing.pos_fname
|
||||
|
||||
let to_string (pos : t) : string =
|
||||
let s, e = pos in
|
||||
Printf.sprintf "in file %s, from %d:%d to %d:%d" s.Lexing.pos_fname s.Lexing.pos_lnum
|
||||
(s.Lexing.pos_cnum - s.Lexing.pos_bol + 1)
|
||||
e.Lexing.pos_lnum
|
||||
(e.Lexing.pos_cnum - e.Lexing.pos_bol + 1)
|
||||
|
||||
let to_string_short (pos : t) : string =
|
||||
let s, e = pos in
|
||||
Printf.sprintf "%s;%d:%d--%d:%d" s.Lexing.pos_fname s.Lexing.pos_lnum
|
||||
(s.Lexing.pos_cnum - s.Lexing.pos_bol + 1)
|
||||
e.Lexing.pos_lnum
|
||||
(e.Lexing.pos_cnum - e.Lexing.pos_bol + 1)
|
||||
|
||||
let indent_number (s : string) : int =
|
||||
try
|
||||
let rec aux (i : int) = if s.[i] = ' ' then aux (i + 1) else i in
|
||||
aux 0
|
||||
with Invalid_argument _ -> String.length s
|
||||
|
||||
let retrieve_loc_text (pos : t) : string =
|
||||
let filename = get_file pos in
|
||||
let blue_style = [ ANSITerminal.Bold; ANSITerminal.blue ] in
|
||||
if filename = "" then "No position information"
|
||||
else
|
||||
let sline = get_start_line pos in
|
||||
let eline = get_end_line pos in
|
||||
let oc =
|
||||
try open_in filename
|
||||
with Sys_error _ ->
|
||||
Cli.error_print (Printf.sprintf "File not found : \"%s\"" filename);
|
||||
exit (-1)
|
||||
in
|
||||
let input_line_opt () : string option = try Some (input_line oc) with End_of_file -> None in
|
||||
let print_matched_line (line : string) (line_no : int) : string =
|
||||
let line_indent = indent_number line in
|
||||
let error_indicator_style = [ ANSITerminal.red; ANSITerminal.Bold ] in
|
||||
line
|
||||
^
|
||||
if line_no >= sline && line_no <= eline then
|
||||
"\n"
|
||||
^
|
||||
if line_no = sline && line_no = eline then
|
||||
Cli.print_with_style error_indicator_style "%*s"
|
||||
(get_end_column pos - 1)
|
||||
(String.make (get_end_column pos - get_start_column pos) '^')
|
||||
else if line_no = sline && line_no <> eline then
|
||||
Cli.print_with_style error_indicator_style "%*s"
|
||||
(String.length line - 1)
|
||||
(String.make (String.length line - get_start_column pos) '^')
|
||||
else if line_no <> sline && line_no <> eline then
|
||||
Cli.print_with_style error_indicator_style "%*s%s" line_indent ""
|
||||
(String.make (String.length line - line_indent) '^')
|
||||
else if line_no <> sline && line_no = eline then
|
||||
Cli.print_with_style error_indicator_style "%*s%*s" line_indent ""
|
||||
(get_end_column pos - 1 - line_indent)
|
||||
(String.make (get_end_column pos - line_indent) '^')
|
||||
else assert false (* should not happen *)
|
||||
else ""
|
||||
in
|
||||
let include_extra_count = 0 in
|
||||
let rec get_lines (n : int) : string list =
|
||||
match input_line_opt () with
|
||||
| Some line ->
|
||||
if n < sline - include_extra_count then get_lines (n + 1)
|
||||
else if n >= sline - include_extra_count && n <= eline + include_extra_count then
|
||||
print_matched_line line n :: get_lines (n + 1)
|
||||
else []
|
||||
| None -> []
|
||||
in
|
||||
let pos_lines = get_lines 1 in
|
||||
let spaces = int_of_float (log10 (float_of_int eline)) + 1 in
|
||||
close_in oc;
|
||||
Cli.print_with_style blue_style "%*s--> %s\n%s" spaces "" filename
|
||||
(Cli.add_prefix_to_each_line
|
||||
(Printf.sprintf "\n%s" (String.concat "\n" pos_lines))
|
||||
(fun i ->
|
||||
let cur_line = sline - include_extra_count + i - 1 in
|
||||
if
|
||||
cur_line >= sline
|
||||
&& cur_line <= sline + (2 * (eline - sline))
|
||||
&& cur_line mod 2 = sline mod 2
|
||||
then Cli.print_with_style blue_style "%*d | " spaces (sline + ((cur_line - sline) / 2))
|
||||
else if cur_line >= sline - include_extra_count && cur_line < sline then
|
||||
Cli.print_with_style blue_style "%*d | " spaces cur_line
|
||||
else if
|
||||
cur_line <= sline + (2 * (eline - sline)) + 1 + include_extra_count
|
||||
&& cur_line > sline + (2 * (eline - sline)) + 1
|
||||
then Cli.print_with_style blue_style "%*d | " spaces (cur_line - (eline - sline + 1))
|
||||
else Cli.print_with_style blue_style "%*s | " spaces ""))
|
||||
|
||||
type 'a marked = 'a * t
|
||||
|
||||
let no_pos : t =
|
||||
let zero_pos =
|
||||
{ Lexing.pos_fname = ""; Lexing.pos_lnum = 0; Lexing.pos_cnum = 0; Lexing.pos_bol = 0 }
|
||||
in
|
||||
(zero_pos, zero_pos)
|
||||
|
||||
let unmark ((x, _) : 'a marked) : 'a = x
|
||||
|
||||
let get_position ((_, x) : 'a marked) : t = x
|
||||
|
||||
let map_under_mark (f : 'a -> 'b) ((x, y) : 'a marked) : 'b marked = (f x, y)
|
||||
|
||||
let same_pos_as (x : 'a) ((_, y) : 'b marked) : 'a marked = (x, y)
|
||||
|
||||
let unmark_option (x : 'a marked option) : 'a option =
|
||||
match x with Some x -> Some (unmark x) | None -> None
|
182
src/catala/scope_language/ast.ml
Normal file
@ -0,0 +1,182 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Abstract syntax tree of the scope language *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Uid = Utils.Uid
|
||||
|
||||
(** {1 Identifiers} *)
|
||||
|
||||
module ScopeName : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module ScopeNameSet : Set.S with type elt = ScopeName.t = Set.Make (ScopeName)
|
||||
|
||||
module ScopeMap : Map.S with type key = ScopeName.t = Map.Make (ScopeName)
|
||||
|
||||
module SubScopeName : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module SubScopeNameSet : Set.S with type elt = SubScopeName.t = Set.Make (SubScopeName)
|
||||
|
||||
module SubScopeMap : Map.S with type key = SubScopeName.t = Map.Make (SubScopeName)
|
||||
|
||||
module ScopeVar : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module ScopeVarSet : Set.S with type elt = ScopeVar.t = Set.Make (ScopeVar)
|
||||
|
||||
module ScopeVarMap : Map.S with type key = ScopeVar.t = Map.Make (ScopeVar)
|
||||
|
||||
module StructName : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module StructMap : Map.S with type key = StructName.t = Map.Make (StructName)
|
||||
|
||||
module StructFieldName : Uid.Id with type info = Uid.MarkedString.info =
|
||||
Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module StructFieldMap : Map.S with type key = StructFieldName.t = Map.Make (StructFieldName)
|
||||
|
||||
module EnumName : Uid.Id with type info = Uid.MarkedString.info = Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module EnumMap : Map.S with type key = EnumName.t = Map.Make (EnumName)
|
||||
|
||||
module EnumConstructor : Uid.Id with type info = Uid.MarkedString.info =
|
||||
Uid.Make (Uid.MarkedString) ()
|
||||
|
||||
module EnumConstructorMap : Map.S with type key = EnumConstructor.t = Map.Make (EnumConstructor)
|
||||
|
||||
type location =
|
||||
| ScopeVar of ScopeVar.t Pos.marked
|
||||
| SubScopeVar of ScopeName.t * SubScopeName.t Pos.marked * ScopeVar.t Pos.marked
|
||||
|
||||
module LocationSet : Set.S with type elt = location Pos.marked = Set.Make (struct
|
||||
type t = location Pos.marked
|
||||
|
||||
let compare x y =
|
||||
match (Pos.unmark x, Pos.unmark y) with
|
||||
| ScopeVar (vx, _), ScopeVar (vy, _) -> ScopeVar.compare vx vy
|
||||
| SubScopeVar (_, (xsubindex, _), (xsubvar, _)), SubScopeVar (_, (ysubindex, _), (ysubvar, _))
|
||||
->
|
||||
let c = SubScopeName.compare xsubindex ysubindex in
|
||||
if c = 0 then ScopeVar.compare xsubvar ysubvar else c
|
||||
| ScopeVar _, SubScopeVar _ -> -1
|
||||
| SubScopeVar _, ScopeVar _ -> 1
|
||||
end)
|
||||
|
||||
(** {1 Abstract syntax tree} *)
|
||||
|
||||
type typ =
|
||||
| TLit of Dcalc.Ast.typ_lit
|
||||
| TStruct of StructName.t
|
||||
| TEnum of EnumName.t
|
||||
| TArrow of typ Pos.marked * typ Pos.marked
|
||||
| TArray of typ
|
||||
| TAny
|
||||
|
||||
(** The expressions use the {{:https://lepigre.fr/ocaml-bindlib/} Bindlib} library, based on
|
||||
higher-order abstract syntax*)
|
||||
type expr =
|
||||
| ELocation of location
|
||||
| EVar of expr Bindlib.var Pos.marked
|
||||
| EStruct of StructName.t * expr Pos.marked StructFieldMap.t
|
||||
| EStructAccess of expr Pos.marked * StructFieldName.t * StructName.t
|
||||
| EEnumInj of expr Pos.marked * EnumConstructor.t * EnumName.t
|
||||
| EMatch of expr Pos.marked * EnumName.t * expr Pos.marked EnumConstructorMap.t
|
||||
| ELit of Dcalc.Ast.lit
|
||||
| EAbs of Pos.t * (expr, expr Pos.marked) Bindlib.mbinder * typ Pos.marked list
|
||||
| EApp of expr Pos.marked * expr Pos.marked list
|
||||
| EOp of Dcalc.Ast.operator
|
||||
| EDefault of expr Pos.marked list * expr Pos.marked * expr Pos.marked
|
||||
| EIfThenElse of expr Pos.marked * expr Pos.marked * expr Pos.marked
|
||||
| EArray of expr Pos.marked list
|
||||
|
||||
let rec locations_used (e : expr Pos.marked) : LocationSet.t =
|
||||
match Pos.unmark e with
|
||||
| ELocation l -> LocationSet.singleton (l, Pos.get_position e)
|
||||
| EVar _ | ELit _ | EOp _ -> LocationSet.empty
|
||||
| EAbs (_, binder, _) ->
|
||||
let _, body = Bindlib.unmbind binder in
|
||||
locations_used body
|
||||
| EStruct (_, es) ->
|
||||
StructFieldMap.fold
|
||||
(fun _ e' acc -> LocationSet.union acc (locations_used e'))
|
||||
es LocationSet.empty
|
||||
| EStructAccess (e1, _, _) -> locations_used e1
|
||||
| EEnumInj (e1, _, _) -> locations_used e1
|
||||
| EMatch (e1, _, es) ->
|
||||
EnumConstructorMap.fold
|
||||
(fun _ e' acc -> LocationSet.union acc (locations_used e'))
|
||||
es (locations_used e1)
|
||||
| EApp (e1, args) ->
|
||||
List.fold_left
|
||||
(fun acc arg -> LocationSet.union (locations_used arg) acc)
|
||||
(locations_used e1) args
|
||||
| EIfThenElse (e1, e2, e3) ->
|
||||
LocationSet.union (locations_used e1)
|
||||
(LocationSet.union (locations_used e2) (locations_used e3))
|
||||
| EDefault (excepts, just, cons) ->
|
||||
List.fold_left
|
||||
(fun acc except -> LocationSet.union (locations_used except) acc)
|
||||
(LocationSet.union (locations_used just) (locations_used cons))
|
||||
excepts
|
||||
| EArray es ->
|
||||
List.fold_left (fun acc e' -> LocationSet.union acc (locations_used e')) LocationSet.empty es
|
||||
|
||||
type rule =
|
||||
| Definition of location Pos.marked * typ Pos.marked * expr Pos.marked
|
||||
| Assertion of expr Pos.marked
|
||||
| Call of ScopeName.t * SubScopeName.t
|
||||
|
||||
type scope_decl = {
|
||||
scope_decl_name : ScopeName.t;
|
||||
scope_sig : typ Pos.marked ScopeVarMap.t;
|
||||
scope_decl_rules : rule list;
|
||||
}
|
||||
|
||||
type struct_ctx = (StructFieldName.t * typ Pos.marked) list StructMap.t
|
||||
|
||||
type enum_ctx = (EnumConstructor.t * typ Pos.marked) list EnumMap.t
|
||||
|
||||
type program = {
|
||||
program_scopes : scope_decl ScopeMap.t;
|
||||
program_enums : enum_ctx;
|
||||
program_structs : struct_ctx;
|
||||
}
|
||||
|
||||
(** {1 Variable helpers} *)
|
||||
|
||||
module Var = struct
|
||||
type t = expr Bindlib.var
|
||||
|
||||
let make (s : string Pos.marked) : t =
|
||||
Bindlib.new_var
|
||||
(fun (x : expr Bindlib.var) : expr -> EVar (x, Pos.get_position s))
|
||||
(Pos.unmark s)
|
||||
|
||||
let compare x y = Bindlib.compare_vars x y
|
||||
end
|
||||
|
||||
type vars = expr Bindlib.mvar
|
||||
|
||||
let make_var ((x, pos) : Var.t Pos.marked) : expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply (fun v -> (v, pos)) (Bindlib.box_var x)
|
||||
|
||||
let make_abs (xs : vars) (e : expr Pos.marked Bindlib.box) (pos_binder : Pos.t)
|
||||
(taus : typ Pos.marked list) (pos : Pos.t) : expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply (fun b -> (EAbs (pos_binder, b, taus), pos)) (Bindlib.bind_mvar xs e)
|
||||
|
||||
let make_app (e : expr Pos.marked Bindlib.box) (u : expr Pos.marked Bindlib.box list) (pos : Pos.t)
|
||||
: expr Pos.marked Bindlib.box =
|
||||
Bindlib.box_apply2 (fun e u -> (EApp (e, u), pos)) e (Bindlib.box_list u)
|
||||
|
||||
module VarMap = Map.Make (Var)
|
228
src/catala/scope_language/dependency.ml
Normal file
@ -0,0 +1,228 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Graph representation of the dependencies between scopes in the Catala program. Vertices are
|
||||
functions, x -> y if x is used in the definition of y. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
|
||||
module SVertex = struct
|
||||
type t = Ast.ScopeName.t
|
||||
|
||||
let hash x = Ast.ScopeName.hash x
|
||||
|
||||
let compare = Ast.ScopeName.compare
|
||||
|
||||
let equal x y = Ast.ScopeName.compare x y = 0
|
||||
|
||||
let format_t (fmt : Format.formatter) (x : t) : unit = Ast.ScopeName.format_t fmt x
|
||||
end
|
||||
|
||||
(** On the edges, the label is the expression responsible for the use of the function *)
|
||||
module SEdge = struct
|
||||
type t = Pos.t
|
||||
|
||||
let compare = compare
|
||||
|
||||
let default = Pos.no_pos
|
||||
end
|
||||
|
||||
module SDependencies = Graph.Persistent.Digraph.ConcreteBidirectionalLabeled (SVertex) (SEdge)
|
||||
module STopologicalTraversal = Graph.Topological.Make (SDependencies)
|
||||
|
||||
module SSCC = Graph.Components.Make (SDependencies)
|
||||
(** Tarjan's stongly connected components algorithm, provided by OCamlGraph *)
|
||||
|
||||
let build_program_dep_graph (prgm : Ast.program) : SDependencies.t =
|
||||
let g = SDependencies.empty in
|
||||
let g = Ast.ScopeMap.fold (fun v _ g -> SDependencies.add_vertex g v) prgm.program_scopes g in
|
||||
Ast.ScopeMap.fold
|
||||
(fun scope_name scope g ->
|
||||
let subscopes =
|
||||
List.fold_left
|
||||
(fun acc r ->
|
||||
match r with
|
||||
| Ast.Definition _ | Ast.Assertion _ -> acc
|
||||
| Ast.Call (subscope, subindex) ->
|
||||
if subscope = scope_name then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The scope %a is calling into itself as a subscope, which is forbidden \
|
||||
since Catala does not provide recursion"
|
||||
Ast.ScopeName.format_t scope.Ast.scope_decl_name)
|
||||
(Pos.get_position (Ast.ScopeName.get_info scope.Ast.scope_decl_name))
|
||||
else
|
||||
Ast.ScopeMap.add subscope
|
||||
(Pos.get_position (Ast.SubScopeName.get_info subindex))
|
||||
acc)
|
||||
Ast.ScopeMap.empty scope.Ast.scope_decl_rules
|
||||
in
|
||||
Ast.ScopeMap.fold
|
||||
(fun subscope pos g ->
|
||||
let edge = SDependencies.E.create subscope pos scope_name in
|
||||
SDependencies.add_edge_e g edge)
|
||||
subscopes g)
|
||||
prgm.program_scopes g
|
||||
|
||||
let check_for_cycle_in_scope (g : SDependencies.t) : unit =
|
||||
(* if there is a cycle, there will be an strongly connected component of cardinality > 1 *)
|
||||
let sccs = SSCC.scc_list g in
|
||||
if List.length sccs < SDependencies.nb_vertex g then
|
||||
let scc = List.find (fun scc -> List.length scc > 1) sccs in
|
||||
Errors.raise_multispanned_error "Cyclic dependency detected between scopes!"
|
||||
(List.flatten
|
||||
(List.map
|
||||
(fun v ->
|
||||
let var_str, var_info =
|
||||
(Format.asprintf "%a" Ast.ScopeName.format_t v, Ast.ScopeName.get_info v)
|
||||
in
|
||||
let succs = SDependencies.succ_e g v in
|
||||
let _, edge_pos, succ = List.find (fun (_, _, succ) -> List.mem succ scc) succs in
|
||||
let succ_str = Format.asprintf "%a" Ast.ScopeName.format_t succ in
|
||||
[
|
||||
(Some ("Cycle variable " ^ var_str ^ ", declared:"), Pos.get_position var_info);
|
||||
( Some ("Used here in the definition of another cycle variable " ^ succ_str ^ ":"),
|
||||
edge_pos );
|
||||
])
|
||||
scc))
|
||||
|
||||
let get_scope_ordering (g : SDependencies.t) : Ast.ScopeName.t list =
|
||||
List.rev (STopologicalTraversal.fold (fun sd acc -> sd :: acc) g [])
|
||||
|
||||
module TVertex = struct
|
||||
type t = Struct of Ast.StructName.t | Enum of Ast.EnumName.t
|
||||
|
||||
let hash x = match x with Struct x -> Ast.StructName.hash x | Enum x -> Ast.EnumName.hash x
|
||||
|
||||
let compare x y =
|
||||
match (x, y) with
|
||||
| Struct x, Struct y -> Ast.StructName.compare x y
|
||||
| Enum x, Enum y -> Ast.EnumName.compare x y
|
||||
| Struct _, Enum _ -> 1
|
||||
| Enum _, Struct _ -> -1
|
||||
|
||||
let equal x y =
|
||||
match (x, y) with
|
||||
| Struct x, Struct y -> Ast.StructName.compare x y = 0
|
||||
| Enum x, Enum y -> Ast.EnumName.compare x y = 0
|
||||
| _ -> false
|
||||
|
||||
let format_t (fmt : Format.formatter) (x : t) : unit =
|
||||
match x with Struct x -> Ast.StructName.format_t fmt x | Enum x -> Ast.EnumName.format_t fmt x
|
||||
|
||||
let get_info (x : t) =
|
||||
match x with Struct x -> Ast.StructName.get_info x | Enum x -> Ast.EnumName.get_info x
|
||||
end
|
||||
|
||||
module TVertexSet = Set.Make (TVertex)
|
||||
|
||||
(** On the edges, the label is the expression responsible for the use of the function *)
|
||||
module TEdge = struct
|
||||
type t = Pos.t
|
||||
|
||||
let compare = compare
|
||||
|
||||
let default = Pos.no_pos
|
||||
end
|
||||
|
||||
module TDependencies = Graph.Persistent.Digraph.ConcreteBidirectionalLabeled (TVertex) (TEdge)
|
||||
module TTopologicalTraversal = Graph.Topological.Make (TDependencies)
|
||||
|
||||
module TSCC = Graph.Components.Make (TDependencies)
|
||||
(** Tarjan's stongly connected components algorithm, provided by OCamlGraph *)
|
||||
|
||||
let rec get_structs_or_enums_in_type (t : Ast.typ Pos.marked) : TVertexSet.t =
|
||||
match Pos.unmark t with
|
||||
| Ast.TStruct s -> TVertexSet.singleton (TVertex.Struct s)
|
||||
| Ast.TEnum e -> TVertexSet.singleton (TVertex.Enum e)
|
||||
| Ast.TArrow (t1, t2) ->
|
||||
TVertexSet.union (get_structs_or_enums_in_type t1) (get_structs_or_enums_in_type t2)
|
||||
| Ast.TLit _ | Ast.TAny -> TVertexSet.empty
|
||||
| Ast.TArray t1 -> get_structs_or_enums_in_type (Pos.same_pos_as t1 t)
|
||||
|
||||
let build_type_graph (structs : Ast.struct_ctx) (enums : Ast.enum_ctx) : TDependencies.t =
|
||||
let g = TDependencies.empty in
|
||||
let g =
|
||||
Ast.StructMap.fold
|
||||
(fun s fields g ->
|
||||
List.fold_left
|
||||
(fun g (_, typ) ->
|
||||
let def = TVertex.Struct s in
|
||||
let g = TDependencies.add_vertex g def in
|
||||
let used = get_structs_or_enums_in_type typ in
|
||||
TVertexSet.fold
|
||||
(fun used g ->
|
||||
if TVertex.equal used def then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The type %a is defined using itself, which is forbidden since Catala does \
|
||||
not provide recursive types"
|
||||
TVertex.format_t used)
|
||||
(Pos.get_position typ)
|
||||
else
|
||||
let edge = TDependencies.E.create used (Pos.get_position typ) def in
|
||||
TDependencies.add_edge_e g edge)
|
||||
used g)
|
||||
g fields)
|
||||
structs g
|
||||
in
|
||||
let g =
|
||||
Ast.EnumMap.fold
|
||||
(fun e cases g ->
|
||||
List.fold_left
|
||||
(fun g (_, typ) ->
|
||||
let def = TVertex.Enum e in
|
||||
let g = TDependencies.add_vertex g def in
|
||||
let used = get_structs_or_enums_in_type typ in
|
||||
TVertexSet.fold
|
||||
(fun used g ->
|
||||
if TVertex.equal used def then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The type %a is defined using itself, which is forbidden since Catala does \
|
||||
not provide recursive types"
|
||||
TVertex.format_t used)
|
||||
(Pos.get_position typ)
|
||||
else
|
||||
let edge = TDependencies.E.create used (Pos.get_position typ) def in
|
||||
TDependencies.add_edge_e g edge)
|
||||
used g)
|
||||
g cases)
|
||||
enums g
|
||||
in
|
||||
g
|
||||
|
||||
let check_type_cycles (structs : Ast.struct_ctx) (enums : Ast.enum_ctx) : unit =
|
||||
let g = build_type_graph structs enums in
|
||||
(* if there is a cycle, there will be an strongly connected component of cardinality > 1 *)
|
||||
let sccs = TSCC.scc_list g in
|
||||
if List.length sccs < TDependencies.nb_vertex g then
|
||||
let scc = List.find (fun scc -> List.length scc > 1) sccs in
|
||||
Errors.raise_multispanned_error "Cyclic dependency detected between types!"
|
||||
(List.flatten
|
||||
(List.map
|
||||
(fun v ->
|
||||
let var_str, var_info =
|
||||
(Format.asprintf "%a" TVertex.format_t v, TVertex.get_info v)
|
||||
in
|
||||
let succs = TDependencies.succ_e g v in
|
||||
let _, edge_pos, succ = List.find (fun (_, _, succ) -> List.mem succ scc) succs in
|
||||
let succ_str = Format.asprintf "%a" TVertex.format_t succ in
|
||||
[
|
||||
(Some ("Cycle type " ^ var_str ^ ", declared:"), Pos.get_position var_info);
|
||||
( Some ("Used here in the definition of another cycle type " ^ succ_str ^ ":"),
|
||||
edge_pos );
|
||||
])
|
||||
scc))
|
8
src/catala/scope_language/dune
Normal file
@ -0,0 +1,8 @@
|
||||
(library
|
||||
(name scopelang)
|
||||
(public_name catala.scopelang)
|
||||
(libraries utils dcalc ocamlgraph))
|
||||
|
||||
(documentation
|
||||
(package catala)
|
||||
(mld_files scopelang))
|
@ -1,32 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** Print a scope program *)
|
||||
let print_scope (scope : Scope_ast.scope) : string =
|
||||
let print_defs (defs : Scope_ast.definition Uid.ScopeDefMap.t) : string =
|
||||
defs |> Uid.ScopeDefMap.bindings
|
||||
|> List.map (fun (uid, term) ->
|
||||
Printf.sprintf "%s:\n%s" (Uid.ScopeDef.format_t uid) (Format_lambda.print_term term))
|
||||
|> String.concat ""
|
||||
in
|
||||
"___Variables Definition___\n" ^ print_defs scope.scope_defs ^ "___Subscope (Re)definition___\n"
|
||||
^ "\n"
|
||||
|
||||
(** Print the whole program *)
|
||||
let print_program (prgm : Scope_ast.program) : string =
|
||||
prgm |> Uid.ScopeMap.bindings
|
||||
|> List.map (fun (uid, scope) ->
|
||||
Printf.sprintf "Scope %s:\n%s" (Uid.Scope.format_t uid) (print_scope scope))
|
||||
|> String.concat "\n"
|
||||
|> Printf.sprintf "Scope program\n%s"
|
124
src/catala/scope_language/print.ml
Normal file
@ -0,0 +1,124 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
open Ast
|
||||
|
||||
let needs_parens (e : expr Pos.marked) : bool =
|
||||
match Pos.unmark e with EAbs _ -> true | _ -> false
|
||||
|
||||
let format_var (fmt : Format.formatter) (v : Var.t) : unit =
|
||||
Format.fprintf fmt "%s_%d" (Bindlib.name_of v) (Bindlib.uid_of v)
|
||||
|
||||
let format_location (fmt : Format.formatter) (l : location) : unit =
|
||||
match l with
|
||||
| ScopeVar v -> Format.fprintf fmt "%a" ScopeVar.format_t (Pos.unmark v)
|
||||
| SubScopeVar (_, subindex, subvar) ->
|
||||
Format.fprintf fmt "%a.%a" SubScopeName.format_t (Pos.unmark subindex) ScopeVar.format_t
|
||||
(Pos.unmark subvar)
|
||||
|
||||
let typ_needs_parens (e : typ Pos.marked) : bool =
|
||||
match Pos.unmark e with TArrow _ -> true | _ -> false
|
||||
|
||||
let rec format_typ (fmt : Format.formatter) (typ : typ Pos.marked) : unit =
|
||||
let format_typ_with_parens (fmt : Format.formatter) (t : typ Pos.marked) =
|
||||
if typ_needs_parens t then Format.fprintf fmt "(%a)" format_typ t
|
||||
else Format.fprintf fmt "%a" format_typ t
|
||||
in
|
||||
match Pos.unmark typ with
|
||||
| TLit l -> Dcalc.Print.format_tlit fmt l
|
||||
| TStruct s -> Format.fprintf fmt "%a" Ast.StructName.format_t s
|
||||
| TEnum e -> Format.fprintf fmt "%a" Ast.EnumName.format_t e
|
||||
| TArrow (t1, t2) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a →@ %a@]" format_typ_with_parens t1 format_typ t2
|
||||
| TArray t1 -> Format.fprintf fmt "@[%a@ array@]" format_typ (Pos.same_pos_as t1 typ)
|
||||
| TAny -> Format.fprintf fmt "any"
|
||||
|
||||
let rec format_expr (fmt : Format.formatter) (e : expr Pos.marked) : unit =
|
||||
let format_with_parens (fmt : Format.formatter) (e : expr Pos.marked) =
|
||||
if needs_parens e then Format.fprintf fmt "(%a)" format_expr e
|
||||
else Format.fprintf fmt "%a" format_expr e
|
||||
in
|
||||
match Pos.unmark e with
|
||||
| ELocation l -> Format.fprintf fmt "%a" format_location l
|
||||
| EVar v -> Format.fprintf fmt "%a" format_var (Pos.unmark v)
|
||||
| ELit l -> Format.fprintf fmt "%a" Dcalc.Print.format_lit (Pos.same_pos_as l e)
|
||||
| EStruct (name, fields) ->
|
||||
Format.fprintf fmt "@[%a @[<hov 2>{@ %a@ }@]@]" Ast.StructName.format_t name
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ";@ ")
|
||||
(fun fmt (field_name, field_expr) ->
|
||||
Format.fprintf fmt "%a = %a" Ast.StructFieldName.format_t field_name format_expr
|
||||
field_expr))
|
||||
(Ast.StructFieldMap.bindings fields)
|
||||
| EStructAccess (e1, field, _) ->
|
||||
Format.fprintf fmt "%a.%a" format_expr e1 Ast.StructFieldName.format_t field
|
||||
| EEnumInj (e1, cons, _) ->
|
||||
Format.fprintf fmt "%a@ %a" Ast.EnumConstructor.format_t cons format_expr e1
|
||||
| EMatch (e1, _, cases) ->
|
||||
Format.fprintf fmt "@[<hov 2>@[match@ %a@ with@]@ %a@]" format_expr e1
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt "@ |@ ")
|
||||
(fun fmt (cons_name, case_expr) ->
|
||||
Format.fprintf fmt "@[<hov 2>%a@ →@ %a@]" Ast.EnumConstructor.format_t cons_name
|
||||
format_expr case_expr))
|
||||
(Ast.EnumConstructorMap.bindings cases)
|
||||
| EApp ((EAbs (_, binder, taus), _), args) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
let xs_tau = List.map2 (fun x tau -> (x, tau)) (Array.to_list xs) taus in
|
||||
let xs_tau_arg = List.map2 (fun (x, tau) arg -> (x, tau, arg)) xs_tau args in
|
||||
Format.fprintf fmt "@[%a%a@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt " ")
|
||||
(fun fmt (x, tau, arg) ->
|
||||
Format.fprintf fmt "@[@[<hov 2>let@ %a@ :@ %a@ =@ %a@]@ in@\n@]" format_var x
|
||||
format_typ tau format_expr arg))
|
||||
xs_tau_arg format_expr body
|
||||
| EAbs (_, binder, taus) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
let xs_tau = List.map2 (fun x tau -> (x, tau)) (Array.to_list xs) taus in
|
||||
Format.fprintf fmt "@[<hov 2>λ@ %a@ →@ %a@]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt " ")
|
||||
(fun fmt (x, tau) -> Format.fprintf fmt "@[(%a:@ %a)@]" format_var x format_typ tau))
|
||||
xs_tau format_expr body
|
||||
| EApp ((EOp (Binop op), _), [ arg1; arg2 ]) ->
|
||||
Format.fprintf fmt "@[%a@ %a@ %a@]" format_with_parens arg1 Dcalc.Print.format_binop
|
||||
(op, Pos.no_pos) format_with_parens arg2
|
||||
| EApp ((EOp (Unop op), _), [ arg1 ]) ->
|
||||
Format.fprintf fmt "@[%a@ %a@]" Dcalc.Print.format_unop (op, Pos.no_pos) format_with_parens
|
||||
arg1
|
||||
| EApp (f, args) ->
|
||||
Format.fprintf fmt "@[%a@ %a@]" format_expr f
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt "@ ") format_with_parens)
|
||||
args
|
||||
| EIfThenElse (e1, e2, e3) ->
|
||||
Format.fprintf fmt "if@ @[<hov 2>%a@]@ then@ @[<hov 2>%a@]@ else@ @[<hov 2>%a@]" format_expr
|
||||
e1 format_expr e2 format_expr e3
|
||||
| EOp (Ternop op) -> Format.fprintf fmt "%a" Dcalc.Print.format_ternop (op, Pos.no_pos)
|
||||
| EOp (Binop op) -> Format.fprintf fmt "%a" Dcalc.Print.format_binop (op, Pos.no_pos)
|
||||
| EOp (Unop op) -> Format.fprintf fmt "%a" Dcalc.Print.format_unop (op, Pos.no_pos)
|
||||
| EDefault (excepts, just, cons) ->
|
||||
if List.length excepts = 0 then
|
||||
Format.fprintf fmt "@[⟨%a ⊢ %a⟩@]" format_expr just format_expr cons
|
||||
else
|
||||
Format.fprintf fmt "@[<hov 2>⟨%a@ |@ %a ⊢ %a⟩@]"
|
||||
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") format_expr)
|
||||
excepts format_expr just format_expr cons
|
||||
| EArray es ->
|
||||
Format.fprintf fmt "[%a]"
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ";")
|
||||
(fun fmt e -> Format.fprintf fmt "@[%a@]" format_expr e))
|
||||
es
|
@ -1,48 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(* Scopes *)
|
||||
type binder = Uid.LocalVar.t
|
||||
|
||||
type definition = Lambda_ast.term
|
||||
|
||||
let empty_def (pos : Pos.t) (typ : Lambda_ast.typ) : definition =
|
||||
((EDefault Lambda_ast.empty_default_term, pos), typ)
|
||||
|
||||
type assertion = Lambda_ast.term
|
||||
|
||||
type variation_typ = Increasing | Decreasing
|
||||
|
||||
type reference_typ = Decree | Law
|
||||
|
||||
type meta_assertion =
|
||||
| FixedBy of reference_typ Pos.marked
|
||||
| VariesWith of Lambda_ast.term * variation_typ Pos.marked option
|
||||
|
||||
type scope = {
|
||||
scope_uid : Uid.Scope.t;
|
||||
scope_defs : definition Uid.ScopeDefMap.t;
|
||||
scope_assertions : assertion list;
|
||||
scope_meta_assertions : meta_assertion list;
|
||||
}
|
||||
|
||||
let empty_scope (uid : Uid.Scope.t) : scope =
|
||||
{
|
||||
scope_uid = uid;
|
||||
scope_defs = Uid.ScopeDefMap.empty;
|
||||
scope_assertions = [];
|
||||
scope_meta_assertions = [];
|
||||
}
|
||||
|
||||
type program = scope Uid.ScopeMap.t
|
@ -1,301 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Nicolas Chataing
|
||||
<nicolas.chataing@ens.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
(** The vertices of the scope dependency graph are either :
|
||||
|
||||
- the variables of the scope ;
|
||||
- the subscopes of the scope.
|
||||
|
||||
Indeed, during interpretation, subscopes are executed atomically.
|
||||
|
||||
In the graph, x -> y if x is used in the definition of y. *)
|
||||
|
||||
module Vertex = struct
|
||||
type t = Var of Uid.Var.t | SubScope of Uid.SubScope.t
|
||||
|
||||
let hash x = match x with Var x -> Uid.Var.hash x | SubScope x -> Uid.SubScope.hash x
|
||||
|
||||
let compare = compare
|
||||
|
||||
let equal x y =
|
||||
match (x, y) with
|
||||
| Var x, Var y -> Uid.Var.compare x y = 0
|
||||
| SubScope x, SubScope y -> Uid.SubScope.compare x y = 0
|
||||
| _ -> false
|
||||
|
||||
let format_t (x : t) : string =
|
||||
match x with Var v -> Uid.Var.format_t v | SubScope v -> Uid.SubScope.format_t v
|
||||
end
|
||||
|
||||
(** On the edges, the label is the expression responsible for the use of the variable *)
|
||||
module Edge = struct
|
||||
type t = Pos.t
|
||||
|
||||
let compare = compare
|
||||
|
||||
let default = Pos.no_pos
|
||||
end
|
||||
|
||||
module ScopeDependencies = Graph.Persistent.Digraph.ConcreteBidirectionalLabeled (Vertex) (Edge)
|
||||
module TopologicalTraversal = Graph.Topological.Make (ScopeDependencies)
|
||||
|
||||
module SCC = Graph.Components.Make (ScopeDependencies)
|
||||
(** Tarjan's stongly connected components algorithm, provided by OCamlGraph *)
|
||||
|
||||
(** Outputs an error in case of cycles. *)
|
||||
let check_for_cycle (g : ScopeDependencies.t) : unit =
|
||||
(* if there is a cycle, there will be an strongly connected component of cardinality > 1 *)
|
||||
let sccs = SCC.scc_list g in
|
||||
if List.length sccs < ScopeDependencies.nb_vertex g then
|
||||
let scc = List.find (fun scc -> List.length scc > 1) sccs in
|
||||
Errors.raise_multispanned_error "cyclic dependency dected between variables!"
|
||||
(List.flatten
|
||||
(List.map
|
||||
(fun v ->
|
||||
let var_str, var_info =
|
||||
match v with
|
||||
| Vertex.Var v -> (Uid.Var.format_t v, Uid.Var.get_info v)
|
||||
| Vertex.SubScope v -> (Uid.SubScope.format_t v, Uid.SubScope.get_info v)
|
||||
in
|
||||
let succs = ScopeDependencies.succ_e g v in
|
||||
let _, edge_pos, succ = List.find (fun (_, _, succ) -> List.mem succ scc) succs in
|
||||
let succ_str =
|
||||
match succ with
|
||||
| Vertex.Var v -> Uid.Var.format_t v
|
||||
| Vertex.SubScope v -> Uid.SubScope.format_t v
|
||||
in
|
||||
[
|
||||
(Some ("cycle variable " ^ var_str ^ ", declared:"), Pos.get_position var_info);
|
||||
( Some ("used here in the definition of another cycle variable " ^ succ_str ^ ":"),
|
||||
edge_pos );
|
||||
])
|
||||
scc))
|
||||
|
||||
let build_scope_dependencies (scope : Scope_ast.scope) (ctxt : Name_resolution.context) :
|
||||
ScopeDependencies.t =
|
||||
let g = ScopeDependencies.empty in
|
||||
let scope_uid = scope.scope_uid in
|
||||
(* Add all the vertices to the graph *)
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_uid ctxt.scopes in
|
||||
let g =
|
||||
Uid.IdentMap.fold
|
||||
(fun _ (v : Uid.Var.t) g -> ScopeDependencies.add_vertex g (Vertex.Var v))
|
||||
scope_ctxt.var_idmap g
|
||||
in
|
||||
let g =
|
||||
Uid.IdentMap.fold
|
||||
(fun _ (v : Uid.SubScope.t) g -> ScopeDependencies.add_vertex g (Vertex.SubScope v))
|
||||
scope_ctxt.sub_scopes_idmap g
|
||||
in
|
||||
let g =
|
||||
Uid.ScopeDefMap.fold
|
||||
(fun def_key def g ->
|
||||
let fv = Lambda_ast.term_fv def in
|
||||
Uid.ScopeDefSet.fold
|
||||
(fun fv_def g ->
|
||||
match (def_key, fv_def) with
|
||||
| Uid.ScopeDef.Var defined, Uid.ScopeDef.Var used ->
|
||||
(* simple case *)
|
||||
ScopeDependencies.add_edge g (Vertex.Var used) (Vertex.Var defined)
|
||||
| Uid.ScopeDef.SubScopeVar (defined, _), Uid.ScopeDef.Var used ->
|
||||
(* here we are defining the input of a subscope using a var of the scope *)
|
||||
ScopeDependencies.add_edge g (Vertex.Var used) (Vertex.SubScope defined)
|
||||
| Uid.ScopeDef.SubScopeVar (defined, _), Uid.ScopeDef.SubScopeVar (used, _) ->
|
||||
(* here we are defining the input of a scope with the output of another subscope *)
|
||||
ScopeDependencies.add_edge g (Vertex.SubScope used) (Vertex.SubScope defined)
|
||||
| Uid.ScopeDef.Var defined, Uid.ScopeDef.SubScopeVar (used, _) ->
|
||||
(* finally we define a scope var with the output of a subscope *)
|
||||
ScopeDependencies.add_edge g (Vertex.SubScope used) (Vertex.Var defined))
|
||||
fv g)
|
||||
scope.scope_defs g
|
||||
in
|
||||
g
|
||||
|
||||
let rec rewrite_subscope_redef_before_call (ctxt : Name_resolution.context)
|
||||
(parent_scope : Uid.Scope.t) (subscope : Scope_ast.scope)
|
||||
((redef, redef_ty) : Scope_ast.definition) : Scope_ast.definition =
|
||||
let parent_scope_ctx = Uid.ScopeMap.find parent_scope ctxt.scopes in
|
||||
let rec_call = rewrite_subscope_redef_before_call ctxt parent_scope subscope in
|
||||
match Pos.unmark redef with
|
||||
| Lambda_ast.EVar (prefix, var) -> (
|
||||
match prefix with
|
||||
| Lambda_ast.NoPrefix ->
|
||||
(* this is a variable of the parent scope, we add the prefix *)
|
||||
( Pos.same_pos_as (Lambda_ast.EVar (Lambda_ast.CallerPrefix (1, None), var)) redef,
|
||||
redef_ty )
|
||||
| Lambda_ast.SubScopePrefix parent_sub ->
|
||||
let parent_sub_real = Uid.SubScopeMap.find parent_sub parent_scope_ctx.sub_scopes in
|
||||
(* two cases here *)
|
||||
if parent_sub_real = subscope.scope_uid then
|
||||
(* we remove the prefix since we're calling this precise subscope *)
|
||||
(Pos.same_pos_as (Lambda_ast.EVar (Lambda_ast.NoPrefix, var)) redef, redef_ty)
|
||||
else
|
||||
(* we add the caller prefix*)
|
||||
( Pos.same_pos_as
|
||||
(Lambda_ast.EVar (Lambda_ast.CallerPrefix (1, Some parent_sub), var))
|
||||
redef,
|
||||
redef_ty )
|
||||
| Lambda_ast.CallerPrefix (i, grand_parent_sub) ->
|
||||
(* In this tricky case, we are trying to call a subscope while being executed as a
|
||||
subscope of a "grand-parent" scope. See [tests/scopes/grand_parent_scope.catala] for an
|
||||
exemple. What we do in this case is that we propagate the prefix while adding 1 to the
|
||||
generation counter *)
|
||||
( Pos.same_pos_as
|
||||
(Lambda_ast.EVar (Lambda_ast.CallerPrefix (i + 1, grand_parent_sub), var))
|
||||
redef,
|
||||
redef_ty ) )
|
||||
| Lambda_ast.EInt _ | Lambda_ast.EBool _ | Lambda_ast.EDec _ | Lambda_ast.EOp _
|
||||
| Lambda_ast.ELocalVar _ ->
|
||||
(redef, redef_ty)
|
||||
| Lambda_ast.EFun (bindings, body) ->
|
||||
(Pos.same_pos_as (Lambda_ast.EFun (bindings, rec_call body)) redef, redef_ty)
|
||||
| Lambda_ast.EApp (f, args) ->
|
||||
(Pos.same_pos_as (Lambda_ast.EApp (rec_call f, List.map rec_call args)) redef, redef_ty)
|
||||
| Lambda_ast.EIfThenElse (if_t, then_t, else_t) ->
|
||||
( Pos.same_pos_as
|
||||
(Lambda_ast.EIfThenElse (rec_call if_t, rec_call then_t, rec_call else_t))
|
||||
redef,
|
||||
redef_ty )
|
||||
| Lambda_ast.EDefault default ->
|
||||
( Pos.same_pos_as
|
||||
(Lambda_ast.EDefault
|
||||
{
|
||||
default with
|
||||
defaults = List.map (fun (x, y) -> (rec_call x, rec_call y)) default.defaults;
|
||||
})
|
||||
redef,
|
||||
redef_ty )
|
||||
|
||||
(** In this function, the keys of the [redefs] maps are variables of the [subscope] *)
|
||||
let merge_var_redefs_before_subscope_call (ctxt : Name_resolution.context)
|
||||
(parent_scope : Uid.Scope.t) (subscope : Scope_ast.scope)
|
||||
(redefs : Scope_ast.definition Uid.VarMap.t) : Scope_ast.scope =
|
||||
let merge_defaults : Lambda_ast.term -> Lambda_ast.term -> Lambda_ast.term =
|
||||
Lambda_ast.map_untype2 (fun old_t new_t ->
|
||||
match (old_t, new_t) with
|
||||
| EDefault old_def, EDefault new_def ->
|
||||
EDefault (Lambda_ast.merge_default_terms old_def new_def)
|
||||
| _ -> assert false
|
||||
(* should not happen *))
|
||||
in
|
||||
(* when merging redefinitions inside a subscope for execution, we need to annotate the variables
|
||||
of the parent scope with the caller prefix *)
|
||||
{
|
||||
subscope with
|
||||
scope_defs =
|
||||
Uid.VarMap.fold
|
||||
(fun new_def_var new_def sub_defs ->
|
||||
let new_def = rewrite_subscope_redef_before_call ctxt parent_scope subscope new_def in
|
||||
match Uid.ScopeDefMap.find_opt (Uid.ScopeDef.Var new_def_var) sub_defs with
|
||||
| None -> Uid.ScopeDefMap.add (Uid.ScopeDef.Var new_def_var) new_def sub_defs
|
||||
| Some old_def ->
|
||||
let def = merge_defaults old_def new_def in
|
||||
Uid.ScopeDefMap.add (Uid.ScopeDef.Var new_def_var) def sub_defs)
|
||||
redefs subscope.scope_defs;
|
||||
}
|
||||
|
||||
let rewrite_context_before_executing_subscope (subscope : Uid.SubScope.t)
|
||||
(exec_context : Lambda_interpreter.exec_context) : Lambda_interpreter.exec_context =
|
||||
Lambda_interpreter.ExecContext.fold
|
||||
(fun key value acc ->
|
||||
match key with
|
||||
| Lambda_interpreter.ExecContextKey.LocalVar _ ->
|
||||
(* we can forget local vars when entering a subscope *)
|
||||
acc
|
||||
| Lambda_interpreter.ExecContextKey.ScopeVar (prefix, var) ->
|
||||
let new_prefix =
|
||||
(* note: this has to match with the behavior of [rewrite_subscope_redef_before_call] *)
|
||||
match prefix with
|
||||
| Lambda_ast.NoPrefix -> Lambda_ast.CallerPrefix (1, None)
|
||||
| Lambda_ast.CallerPrefix (i, sub) -> Lambda_ast.CallerPrefix (i + 1, sub)
|
||||
| Lambda_ast.SubScopePrefix sub ->
|
||||
if sub = subscope then Lambda_ast.NoPrefix else Lambda_ast.CallerPrefix (1, Some sub)
|
||||
in
|
||||
Lambda_interpreter.ExecContext.add
|
||||
(Lambda_interpreter.ExecContextKey.ScopeVar (new_prefix, var))
|
||||
value acc)
|
||||
exec_context Lambda_interpreter.ExecContext.empty
|
||||
|
||||
let rewrite_context_after_executing_subscope (subscope : Uid.SubScope.t)
|
||||
(exec_context : Lambda_interpreter.exec_context) : Lambda_interpreter.exec_context =
|
||||
Lambda_interpreter.ExecContext.fold
|
||||
(fun key value acc ->
|
||||
match key with
|
||||
| Lambda_interpreter.ExecContextKey.LocalVar _ ->
|
||||
(* we can forget local vars when entering a subscope *)
|
||||
acc
|
||||
| Lambda_interpreter.ExecContextKey.ScopeVar (prefix, var) -> (
|
||||
let new_prefix =
|
||||
match prefix with
|
||||
| Lambda_ast.NoPrefix -> Some (Lambda_ast.SubScopePrefix subscope)
|
||||
| Lambda_ast.CallerPrefix (i, sub) -> (
|
||||
if i > 1 then Some (Lambda_ast.CallerPrefix (i - 1, sub))
|
||||
else
|
||||
match sub with
|
||||
| None -> Some Lambda_ast.NoPrefix
|
||||
| Some sub -> Some (Lambda_ast.SubScopePrefix sub) )
|
||||
| Lambda_ast.SubScopePrefix _ -> None
|
||||
(* we drop the subscope's subscopes since they can't be accessed *)
|
||||
in
|
||||
match new_prefix with
|
||||
| None -> acc
|
||||
| Some new_prefix ->
|
||||
Lambda_interpreter.ExecContext.add
|
||||
(Lambda_interpreter.ExecContextKey.ScopeVar (new_prefix, var))
|
||||
value acc ))
|
||||
exec_context Lambda_interpreter.ExecContext.empty
|
||||
|
||||
let rec execute_scope ?(exec_context = Lambda_interpreter.empty_exec_ctxt)
|
||||
(ctxt : Name_resolution.context) (prgm : Scope_ast.program) (scope_prgm : Scope_ast.scope) :
|
||||
Lambda_interpreter.exec_context =
|
||||
let scope_ctxt = Uid.ScopeMap.find scope_prgm.scope_uid ctxt.scopes in
|
||||
let deps = build_scope_dependencies scope_prgm ctxt in
|
||||
check_for_cycle deps;
|
||||
TopologicalTraversal.fold
|
||||
(fun v exec_context ->
|
||||
match v with
|
||||
| Vertex.Var var -> (
|
||||
match Uid.ScopeDefMap.find_opt (Uid.ScopeDef.Var var) scope_prgm.scope_defs with
|
||||
| Some def ->
|
||||
(* we evaluate a variable of the scope, no tricky business here *)
|
||||
Lambda_interpreter.ExecContext.add
|
||||
(Lambda_interpreter.ExecContextKey.ScopeVar (Lambda_ast.NoPrefix, var))
|
||||
( Lambda_interpreter.eval_term (Uid.ScopeDef.Var var) exec_context def
|
||||
|> Lambda_ast.untype )
|
||||
exec_context
|
||||
| None -> assert false (* should not happen *) )
|
||||
| Vertex.SubScope subscope_uid ->
|
||||
(* this is the tricky case where we have to the the bookkeeping of rewriting the context
|
||||
and additional defaults that we pass for the subscope for execution. See formalization
|
||||
for more details *)
|
||||
let subscope_real_uid = Uid.SubScopeMap.find subscope_uid scope_ctxt.sub_scopes in
|
||||
let subscope = Uid.ScopeMap.find subscope_real_uid prgm in
|
||||
let redefs_to_include_to_subscope =
|
||||
Uid.ScopeDefMap.fold
|
||||
(fun def_key def acc ->
|
||||
match def_key with
|
||||
| Uid.ScopeDef.Var _ -> acc
|
||||
| Uid.ScopeDef.SubScopeVar (def_sub_uid, var) ->
|
||||
if def_sub_uid = subscope_uid then Uid.VarMap.add var def acc else acc)
|
||||
scope_prgm.scope_defs Uid.VarMap.empty
|
||||
in
|
||||
let subscope =
|
||||
merge_var_redefs_before_subscope_call ctxt scope_prgm.scope_uid subscope
|
||||
redefs_to_include_to_subscope
|
||||
in
|
||||
let exec_context = rewrite_context_before_executing_subscope subscope_uid exec_context in
|
||||
let exec_context = execute_scope ~exec_context ctxt prgm subscope in
|
||||
let exec_context = rewrite_context_after_executing_subscope subscope_uid exec_context in
|
||||
exec_context)
|
||||
deps exec_context
|
550
src/catala/scope_language/scope_to_dcalc.ml
Normal file
@ -0,0 +1,550 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module Pos = Utils.Pos
|
||||
module Errors = Utils.Errors
|
||||
module Cli = Utils.Cli
|
||||
|
||||
type scope_sigs_ctx = ((Ast.ScopeVar.t * Dcalc.Ast.typ) list * Dcalc.Ast.Var.t) Ast.ScopeMap.t
|
||||
|
||||
type ctx = {
|
||||
structs : Ast.struct_ctx;
|
||||
enums : Ast.enum_ctx;
|
||||
scope_name : Ast.ScopeName.t;
|
||||
scopes_parameters : scope_sigs_ctx;
|
||||
scope_vars : (Dcalc.Ast.Var.t * Dcalc.Ast.typ) Ast.ScopeVarMap.t;
|
||||
subscope_vars : (Dcalc.Ast.Var.t * Dcalc.Ast.typ) Ast.ScopeVarMap.t Ast.SubScopeMap.t;
|
||||
local_vars : Dcalc.Ast.Var.t Ast.VarMap.t;
|
||||
}
|
||||
|
||||
let empty_ctx (struct_ctx : Ast.struct_ctx) (enum_ctx : Ast.enum_ctx) (scopes_ctx : scope_sigs_ctx)
|
||||
(scope_name : Ast.ScopeName.t) =
|
||||
{
|
||||
structs = struct_ctx;
|
||||
enums = enum_ctx;
|
||||
scope_name;
|
||||
scopes_parameters = scopes_ctx;
|
||||
scope_vars = Ast.ScopeVarMap.empty;
|
||||
subscope_vars = Ast.SubScopeMap.empty;
|
||||
local_vars = Ast.VarMap.empty;
|
||||
}
|
||||
|
||||
type scope_ctx = Dcalc.Ast.Var.t Ast.ScopeMap.t
|
||||
|
||||
let hole_var : Dcalc.Ast.Var.t = Dcalc.Ast.Var.make ("·", Pos.no_pos)
|
||||
|
||||
let rec translate_typ (ctx : ctx) (t : Ast.typ Pos.marked) : Dcalc.Ast.typ Pos.marked =
|
||||
Pos.same_pos_as
|
||||
( match Pos.unmark t with
|
||||
| Ast.TLit l -> Dcalc.Ast.TLit l
|
||||
| Ast.TArrow (t1, t2) -> Dcalc.Ast.TArrow (translate_typ ctx t1, translate_typ ctx t2)
|
||||
| Ast.TStruct s_uid ->
|
||||
let s_fields = Ast.StructMap.find s_uid ctx.structs in
|
||||
Dcalc.Ast.TTuple (List.map (fun (_, t) -> translate_typ ctx t) s_fields)
|
||||
| Ast.TEnum e_uid ->
|
||||
let e_cases = Ast.EnumMap.find e_uid ctx.enums in
|
||||
Dcalc.Ast.TEnum (List.map (fun (_, t) -> translate_typ ctx t) e_cases)
|
||||
| Ast.TArray t1 -> Dcalc.Ast.TArray (translate_typ ctx (Pos.same_pos_as t1 t))
|
||||
| Ast.TAny -> Dcalc.Ast.TAny )
|
||||
t
|
||||
|
||||
let merge_defaults (caller : Dcalc.Ast.expr Pos.marked Bindlib.box)
|
||||
(callee : Dcalc.Ast.expr Pos.marked Bindlib.box) : Dcalc.Ast.expr Pos.marked Bindlib.box =
|
||||
let caller =
|
||||
Dcalc.Ast.make_app caller
|
||||
[ Bindlib.box (Dcalc.Ast.ELit Dcalc.Ast.LUnit, Pos.no_pos) ]
|
||||
Pos.no_pos
|
||||
in
|
||||
let body =
|
||||
Bindlib.box_apply2
|
||||
(fun caller callee ->
|
||||
( Dcalc.Ast.EDefault
|
||||
([ caller ], (Dcalc.Ast.ELit (Dcalc.Ast.LBool true), Pos.no_pos), callee),
|
||||
Pos.no_pos ))
|
||||
caller callee
|
||||
in
|
||||
body
|
||||
|
||||
let rec translate_expr (ctx : ctx) (e : Ast.expr Pos.marked) : Dcalc.Ast.expr Pos.marked Bindlib.box
|
||||
=
|
||||
Bindlib.box_apply
|
||||
(fun (x : Dcalc.Ast.expr) -> Pos.same_pos_as x e)
|
||||
( match Pos.unmark e with
|
||||
| EVar v -> Bindlib.box_var (Ast.VarMap.find (Pos.unmark v) ctx.local_vars)
|
||||
| ELit l -> Bindlib.box (Dcalc.Ast.ELit l)
|
||||
| EStruct (struct_name, e_fields) ->
|
||||
let struct_sig = Ast.StructMap.find struct_name ctx.structs in
|
||||
let d_fields, remaining_e_fields =
|
||||
List.fold_right
|
||||
(fun (field_name, _) (d_fields, e_fields) ->
|
||||
let field_e =
|
||||
try Ast.StructFieldMap.find field_name e_fields
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "Missing field for structure %a: \"%a\""
|
||||
Ast.StructName.format_t struct_name Ast.StructFieldName.format_t field_name)
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let field_d = translate_expr ctx field_e in
|
||||
let field_d =
|
||||
Bindlib.box_apply
|
||||
(fun field_d -> (field_d, Some (Ast.StructFieldName.get_info field_name)))
|
||||
field_d
|
||||
in
|
||||
(field_d :: d_fields, Ast.StructFieldMap.remove field_name e_fields))
|
||||
struct_sig ([], e_fields)
|
||||
in
|
||||
if Ast.StructFieldMap.cardinal remaining_e_fields > 0 then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "The fields \"%a\" do not belong to the structure %a"
|
||||
Ast.StructName.format_t struct_name
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ", ")
|
||||
(fun fmt (field_name, _) ->
|
||||
Format.fprintf fmt "%a" Ast.StructFieldName.format_t field_name))
|
||||
(Ast.StructFieldMap.bindings remaining_e_fields))
|
||||
(Pos.get_position e)
|
||||
else
|
||||
Bindlib.box_apply (fun d_fields -> Dcalc.Ast.ETuple d_fields) (Bindlib.box_list d_fields)
|
||||
| EStructAccess (e1, field_name, struct_name) ->
|
||||
let struct_sig = Ast.StructMap.find struct_name ctx.structs in
|
||||
let _, field_index =
|
||||
try List.assoc field_name (List.mapi (fun i (x, y) -> (x, (y, i))) struct_sig)
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "The field \"%a\" does not belong to the structure %a"
|
||||
Ast.StructFieldName.format_t field_name Ast.StructName.format_t struct_name)
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let e1 = translate_expr ctx e1 in
|
||||
Bindlib.box_apply
|
||||
(fun e1 ->
|
||||
Dcalc.Ast.ETupleAccess (e1, field_index, Some (Ast.StructFieldName.get_info field_name)))
|
||||
e1
|
||||
| EEnumInj (e1, constructor, enum_name) ->
|
||||
let enum_sig = Ast.EnumMap.find enum_name ctx.enums in
|
||||
let _, constructor_index =
|
||||
try List.assoc constructor (List.mapi (fun i (x, y) -> (x, (y, i))) enum_sig)
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "The constructor \"%a\" does not belong to the enum %a"
|
||||
Ast.EnumConstructor.format_t constructor Ast.EnumName.format_t enum_name)
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let e1 = translate_expr ctx e1 in
|
||||
Bindlib.box_apply
|
||||
(fun e1 ->
|
||||
Dcalc.Ast.EInj
|
||||
( e1,
|
||||
constructor_index,
|
||||
Ast.EnumConstructor.get_info constructor,
|
||||
List.map (fun (_, t) -> translate_typ ctx t) enum_sig ))
|
||||
e1
|
||||
| EMatch (e1, enum_name, cases) ->
|
||||
let enum_sig = Ast.EnumMap.find enum_name ctx.enums in
|
||||
let d_cases, remaining_e_cases =
|
||||
List.fold_right
|
||||
(fun (constructor, _) (d_cases, e_cases) ->
|
||||
let case_e =
|
||||
try Ast.EnumConstructorMap.find constructor e_cases
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The constructor %a of enum %a is missing from this pattern matching"
|
||||
Ast.EnumConstructor.format_t constructor Ast.EnumName.format_t enum_name)
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let case_d = translate_expr ctx case_e in
|
||||
let case_d =
|
||||
Bindlib.box_apply
|
||||
(fun case_d -> (case_d, Ast.EnumConstructor.get_info constructor))
|
||||
case_d
|
||||
in
|
||||
(case_d :: d_cases, Ast.EnumConstructorMap.remove constructor e_cases))
|
||||
enum_sig ([], cases)
|
||||
in
|
||||
if Ast.EnumConstructorMap.cardinal remaining_e_cases > 0 then
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf "Patter matching is incomplete for enum %a: missing cases %a"
|
||||
Ast.EnumName.format_t enum_name
|
||||
(Format.pp_print_list
|
||||
~pp_sep:(fun fmt () -> Format.fprintf fmt ", ")
|
||||
(fun fmt (case_name, _) ->
|
||||
Format.fprintf fmt "%a" Ast.EnumConstructor.format_t case_name))
|
||||
(Ast.EnumConstructorMap.bindings remaining_e_cases))
|
||||
(Pos.get_position e)
|
||||
else
|
||||
let e1 = translate_expr ctx e1 in
|
||||
Bindlib.box_apply2
|
||||
(fun d_fields e1 -> Dcalc.Ast.EMatch (e1, d_fields))
|
||||
(Bindlib.box_list d_cases) e1
|
||||
| EApp (e1, args) ->
|
||||
Bindlib.box_apply2
|
||||
(fun e u -> Dcalc.Ast.EApp (e, u))
|
||||
(translate_expr ctx e1)
|
||||
(Bindlib.box_list (List.map (translate_expr ctx) args))
|
||||
| EAbs (pos_binder, binder, typ) ->
|
||||
let xs, body = Bindlib.unmbind binder in
|
||||
let new_xs = Array.map (fun x -> Dcalc.Ast.Var.make (Bindlib.name_of x, Pos.no_pos)) xs in
|
||||
let both_xs = Array.map2 (fun x new_x -> (x, new_x)) xs new_xs in
|
||||
let body =
|
||||
translate_expr
|
||||
{
|
||||
ctx with
|
||||
local_vars =
|
||||
Array.fold_left
|
||||
(fun local_vars (x, new_x) -> Ast.VarMap.add x new_x local_vars)
|
||||
ctx.local_vars both_xs;
|
||||
}
|
||||
body
|
||||
in
|
||||
let binder = Bindlib.bind_mvar new_xs body in
|
||||
Bindlib.box_apply
|
||||
(fun b -> Dcalc.Ast.EAbs (pos_binder, b, List.map (translate_typ ctx) typ))
|
||||
binder
|
||||
| EDefault (excepts, just, cons) ->
|
||||
Bindlib.box_apply3
|
||||
(fun e j c -> Dcalc.Ast.EDefault (e, j, c))
|
||||
(Bindlib.box_list (List.map (translate_expr ctx) excepts))
|
||||
(translate_expr ctx just) (translate_expr ctx cons)
|
||||
| ELocation (ScopeVar a) ->
|
||||
Bindlib.box_var (fst (Ast.ScopeVarMap.find (Pos.unmark a) ctx.scope_vars))
|
||||
| ELocation (SubScopeVar (_, s, a)) -> (
|
||||
try
|
||||
Bindlib.box_var
|
||||
(fst
|
||||
(Ast.ScopeVarMap.find (Pos.unmark a)
|
||||
(Ast.SubScopeMap.find (Pos.unmark s) ctx.subscope_vars)))
|
||||
with Not_found ->
|
||||
Errors.raise_spanned_error
|
||||
(Format.asprintf
|
||||
"The variable %a.%a cannot be used here,\n\
|
||||
as subscope %a's results will not have been computed yet" Ast.SubScopeName.format_t
|
||||
(Pos.unmark s) Ast.ScopeVar.format_t (Pos.unmark a) Ast.SubScopeName.format_t
|
||||
(Pos.unmark s))
|
||||
(Pos.get_position e) )
|
||||
| EIfThenElse (cond, et, ef) ->
|
||||
Bindlib.box_apply3
|
||||
(fun c t f -> Dcalc.Ast.EIfThenElse (c, t, f))
|
||||
(translate_expr ctx cond) (translate_expr ctx et) (translate_expr ctx ef)
|
||||
| EOp op -> Bindlib.box (Dcalc.Ast.EOp op)
|
||||
| EArray es ->
|
||||
Bindlib.box_apply
|
||||
(fun es -> Dcalc.Ast.EArray es)
|
||||
(Bindlib.box_list (List.map (translate_expr ctx) es)) )
|
||||
|
||||
let rec translate_rule (ctx : ctx) (rule : Ast.rule) (rest : Ast.rule list)
|
||||
((sigma_name, pos_sigma) : Utils.Uid.MarkedString.info) :
|
||||
Dcalc.Ast.expr Pos.marked Bindlib.box * ctx =
|
||||
match rule with
|
||||
| Definition ((ScopeVar a, var_def_pos), tau, e) ->
|
||||
let a_name = Ast.ScopeVar.get_info (Pos.unmark a) in
|
||||
let a_var = Dcalc.Ast.Var.make a_name in
|
||||
let tau = translate_typ ctx tau in
|
||||
let new_ctx =
|
||||
{
|
||||
ctx with
|
||||
scope_vars = Ast.ScopeVarMap.add (Pos.unmark a) (a_var, Pos.unmark tau) ctx.scope_vars;
|
||||
}
|
||||
in
|
||||
let next_e, new_ctx = translate_rules new_ctx rest (sigma_name, pos_sigma) in
|
||||
let new_e = translate_expr ctx e in
|
||||
let a_expr = Dcalc.Ast.make_var (a_var, var_def_pos) in
|
||||
let merged_expr = merge_defaults a_expr new_e in
|
||||
let merged_expr =
|
||||
Bindlib.box_apply
|
||||
(fun merged_expr ->
|
||||
( Dcalc.Ast.EApp
|
||||
( (Dcalc.Ast.EOp (Dcalc.Ast.Unop Dcalc.Ast.ErrorOnEmpty), Pos.get_position a_name),
|
||||
[ merged_expr ] ),
|
||||
Pos.get_position merged_expr ))
|
||||
merged_expr
|
||||
in
|
||||
let merged_expr =
|
||||
Bindlib.box_apply
|
||||
(fun merged_expr ->
|
||||
( Dcalc.Ast.EApp
|
||||
( ( Dcalc.Ast.EOp
|
||||
(Dcalc.Ast.Unop
|
||||
(Dcalc.Ast.Log (Dcalc.Ast.VarDef, [ (sigma_name, pos_sigma); a_name ]))),
|
||||
Pos.get_position a_name ),
|
||||
[ merged_expr ] ),
|
||||
Pos.get_position merged_expr ))
|
||||
merged_expr
|
||||
in
|
||||
let next_e = Dcalc.Ast.make_let_in a_var tau merged_expr next_e in
|
||||
(next_e, new_ctx)
|
||||
| Definition ((SubScopeVar (_subs_name, subs_index, subs_var), var_def_pos), tau, e) ->
|
||||
let a_name =
|
||||
Pos.map_under_mark
|
||||
(fun str -> str ^ "." ^ Pos.unmark (Ast.ScopeVar.get_info (Pos.unmark subs_var)))
|
||||
(Ast.SubScopeName.get_info (Pos.unmark subs_index))
|
||||
in
|
||||
let a_var = (Dcalc.Ast.Var.make a_name, var_def_pos) in
|
||||
let tau = translate_typ ctx tau in
|
||||
let new_ctx =
|
||||
{
|
||||
ctx with
|
||||
subscope_vars =
|
||||
Ast.SubScopeMap.update (Pos.unmark subs_index)
|
||||
(fun map ->
|
||||
match map with
|
||||
| Some map ->
|
||||
Some
|
||||
(Ast.ScopeVarMap.add (Pos.unmark subs_var)
|
||||
(Pos.unmark a_var, Pos.unmark tau)
|
||||
map)
|
||||
| None ->
|
||||
Some
|
||||
(Ast.ScopeVarMap.singleton (Pos.unmark subs_var)
|
||||
(Pos.unmark a_var, Pos.unmark tau)))
|
||||
ctx.subscope_vars;
|
||||
}
|
||||
in
|
||||
let next_e, new_ctx = translate_rules new_ctx rest (sigma_name, pos_sigma) in
|
||||
let intermediate_e =
|
||||
Dcalc.Ast.make_abs
|
||||
(Array.of_list [ Pos.unmark a_var ])
|
||||
next_e var_def_pos
|
||||
[ (Dcalc.Ast.TArrow ((TLit TUnit, var_def_pos), tau), var_def_pos) ]
|
||||
(Pos.get_position e)
|
||||
in
|
||||
let new_e = translate_expr ctx e in
|
||||
let new_e =
|
||||
Bindlib.box_apply
|
||||
(fun new_e ->
|
||||
( Dcalc.Ast.EApp
|
||||
( ( Dcalc.Ast.EOp
|
||||
(Dcalc.Ast.Unop
|
||||
(Dcalc.Ast.Log (Dcalc.Ast.VarDef, [ (sigma_name, pos_sigma); a_name ]))),
|
||||
Pos.get_position a_name ),
|
||||
[ new_e ] ),
|
||||
Pos.get_position new_e ))
|
||||
new_e
|
||||
in
|
||||
let silent_var = Dcalc.Ast.Var.make ("_", Pos.no_pos) in
|
||||
let thunked_new_e =
|
||||
Dcalc.Ast.make_abs
|
||||
(Array.of_list [ silent_var ])
|
||||
new_e var_def_pos
|
||||
[ (Dcalc.Ast.TLit TUnit, var_def_pos) ]
|
||||
var_def_pos
|
||||
in
|
||||
let out_e = Dcalc.Ast.make_app intermediate_e [ thunked_new_e ] (Pos.get_position e) in
|
||||
(out_e, new_ctx)
|
||||
| Call (subname, subindex) ->
|
||||
let all_subscope_vars, scope_dcalc_var = Ast.ScopeMap.find subname ctx.scopes_parameters in
|
||||
let subscope_vars_defined =
|
||||
try Ast.SubScopeMap.find subindex ctx.subscope_vars
|
||||
with Not_found -> Ast.ScopeVarMap.empty
|
||||
in
|
||||
let subscope_var_not_yet_defined subvar =
|
||||
not (Ast.ScopeVarMap.mem subvar subscope_vars_defined)
|
||||
in
|
||||
let subscope_args =
|
||||
List.map
|
||||
(fun (subvar, _) ->
|
||||
if subscope_var_not_yet_defined subvar then
|
||||
Bindlib.box Dcalc.Interpreter.empty_thunked_term
|
||||
else
|
||||
let a_var, _ = Ast.ScopeVarMap.find subvar subscope_vars_defined in
|
||||
Dcalc.Ast.make_var (a_var, Pos.get_position (Ast.SubScopeName.get_info subindex)))
|
||||
all_subscope_vars
|
||||
in
|
||||
let all_subscope_vars_dcalc =
|
||||
List.map
|
||||
(fun (subvar, tau) ->
|
||||
let sub_dcalc_var =
|
||||
Dcalc.Ast.Var.make
|
||||
(Pos.map_under_mark
|
||||
(fun s -> Pos.unmark (Ast.SubScopeName.get_info subindex) ^ "." ^ s)
|
||||
(Ast.ScopeVar.get_info subvar))
|
||||
in
|
||||
(subvar, tau, sub_dcalc_var))
|
||||
all_subscope_vars
|
||||
in
|
||||
let new_ctx =
|
||||
{
|
||||
ctx with
|
||||
subscope_vars =
|
||||
Ast.SubScopeMap.add subindex
|
||||
(List.fold_left
|
||||
(fun acc (var, tau, dvar) -> Ast.ScopeVarMap.add var (dvar, tau) acc)
|
||||
Ast.ScopeVarMap.empty all_subscope_vars_dcalc)
|
||||
ctx.subscope_vars;
|
||||
}
|
||||
in
|
||||
let subscope_func =
|
||||
Dcalc.Ast.make_var (scope_dcalc_var, Pos.get_position (Ast.SubScopeName.get_info subindex))
|
||||
in
|
||||
let subscope_func =
|
||||
Bindlib.box_apply
|
||||
(fun subscope_func ->
|
||||
( Dcalc.Ast.EApp
|
||||
( ( Dcalc.Ast.EOp
|
||||
(Dcalc.Ast.Unop
|
||||
(Dcalc.Ast.Log
|
||||
( Dcalc.Ast.BeginCall,
|
||||
[
|
||||
(sigma_name, pos_sigma);
|
||||
Ast.SubScopeName.get_info subindex;
|
||||
Ast.ScopeName.get_info subname;
|
||||
] ))),
|
||||
Pos.get_position subscope_func ),
|
||||
[ subscope_func ] ),
|
||||
Pos.get_position subscope_func ))
|
||||
subscope_func
|
||||
in
|
||||
let call_expr =
|
||||
Bindlib.box_apply2
|
||||
(fun e u -> (Dcalc.Ast.EApp (e, u), Pos.no_pos))
|
||||
subscope_func (Bindlib.box_list subscope_args)
|
||||
in
|
||||
let call_expr =
|
||||
Bindlib.box_apply
|
||||
(fun call_expr ->
|
||||
( Dcalc.Ast.EApp
|
||||
( ( Dcalc.Ast.EOp
|
||||
(Dcalc.Ast.Unop
|
||||
(Dcalc.Ast.Log
|
||||
( Dcalc.Ast.EndCall,
|
||||
[
|
||||
(sigma_name, pos_sigma);
|
||||
Ast.SubScopeName.get_info subindex;
|
||||
Ast.ScopeName.get_info subname;
|
||||
] ))),
|
||||
Pos.get_position call_expr ),
|
||||
[ call_expr ] ),
|
||||
Pos.get_position call_expr ))
|
||||
call_expr
|
||||
in
|
||||
let result_tuple_var = Dcalc.Ast.Var.make ("result", Pos.no_pos) in
|
||||
let next_e, new_ctx = translate_rules new_ctx rest (sigma_name, pos_sigma) in
|
||||
let results_bindings, _ =
|
||||
List.fold_right
|
||||
(fun (_, tau, dvar) (acc, i) ->
|
||||
let result_access =
|
||||
Bindlib.box_apply
|
||||
(fun r -> (Dcalc.Ast.ETupleAccess (r, i, None), pos_sigma))
|
||||
(Dcalc.Ast.make_var (result_tuple_var, pos_sigma))
|
||||
in
|
||||
(Dcalc.Ast.make_let_in dvar (tau, pos_sigma) result_access acc, i - 1))
|
||||
all_subscope_vars_dcalc
|
||||
(next_e, List.length all_subscope_vars_dcalc - 1)
|
||||
in
|
||||
|
||||
let result_tuple_typ =
|
||||
( Dcalc.Ast.TTuple (List.map (fun (_, tau, _) -> (tau, pos_sigma)) all_subscope_vars_dcalc),
|
||||
pos_sigma )
|
||||
in
|
||||
(Dcalc.Ast.make_let_in result_tuple_var result_tuple_typ call_expr results_bindings, new_ctx)
|
||||
| Assertion e ->
|
||||
let next_e, new_ctx = translate_rules ctx rest (sigma_name, pos_sigma) in
|
||||
let new_e = translate_expr ctx e in
|
||||
( Dcalc.Ast.make_let_in
|
||||
(Dcalc.Ast.Var.make ("_", Pos.no_pos))
|
||||
(Dcalc.Ast.TLit TUnit, Pos.no_pos)
|
||||
(Bindlib.box_apply (fun new_e -> Pos.same_pos_as (Dcalc.Ast.EAssert new_e) e) new_e)
|
||||
next_e,
|
||||
new_ctx )
|
||||
|
||||
and translate_rules (ctx : ctx) (rules : Ast.rule list)
|
||||
((sigma_name, pos_sigma) : Utils.Uid.MarkedString.info) :
|
||||
Dcalc.Ast.expr Pos.marked Bindlib.box * ctx =
|
||||
match rules with
|
||||
| [] ->
|
||||
let scope_variables = Ast.ScopeVarMap.bindings ctx.scope_vars in
|
||||
let return_exp =
|
||||
Bindlib.box_apply
|
||||
(fun args -> (Dcalc.Ast.ETuple (List.map (fun arg -> (arg, None)) args), pos_sigma))
|
||||
(Bindlib.box_list
|
||||
(List.map
|
||||
(fun (_, (dcalc_var, _)) -> Dcalc.Ast.make_var (dcalc_var, pos_sigma))
|
||||
scope_variables))
|
||||
in
|
||||
(return_exp, ctx)
|
||||
| hd :: tl -> translate_rule ctx hd tl (sigma_name, pos_sigma)
|
||||
|
||||
let translate_scope_decl (struct_ctx : Ast.struct_ctx) (enum_ctx : Ast.enum_ctx)
|
||||
(sctx : scope_sigs_ctx) (scope_name : Ast.ScopeName.t) (sigma : Ast.scope_decl) :
|
||||
Dcalc.Ast.expr Pos.marked Bindlib.box =
|
||||
let ctx = empty_ctx struct_ctx enum_ctx sctx scope_name in
|
||||
let sigma_info = Ast.ScopeName.get_info sigma.scope_decl_name in
|
||||
let rules, ctx = translate_rules ctx sigma.scope_decl_rules sigma_info in
|
||||
let scope_variables, _ = Ast.ScopeMap.find sigma.scope_decl_name sctx in
|
||||
let scope_variables =
|
||||
List.map
|
||||
(fun (x, tau) ->
|
||||
let dcalc_x, _ = Ast.ScopeVarMap.find x ctx.scope_vars in
|
||||
(x, tau, dcalc_x))
|
||||
scope_variables
|
||||
in
|
||||
let pos_sigma = Pos.get_position sigma_info in
|
||||
Dcalc.Ast.make_abs
|
||||
(Array.of_list (List.map (fun (_, _, x) -> x) scope_variables))
|
||||
rules pos_sigma
|
||||
(List.map
|
||||
(fun (_, tau, _) ->
|
||||
(Dcalc.Ast.TArrow ((Dcalc.Ast.TLit TUnit, pos_sigma), (tau, pos_sigma)), pos_sigma))
|
||||
scope_variables)
|
||||
pos_sigma
|
||||
|
||||
let build_scope_typ_from_sig (scope_sig : (Ast.ScopeVar.t * Dcalc.Ast.typ) list) (pos : Pos.t) :
|
||||
Dcalc.Ast.typ Pos.marked =
|
||||
let result_typ = (Dcalc.Ast.TTuple (List.map (fun (_, tau) -> (tau, pos)) scope_sig), pos) in
|
||||
List.fold_right
|
||||
(fun (_, arg_t) acc ->
|
||||
(Dcalc.Ast.TArrow ((Dcalc.Ast.TArrow ((TLit TUnit, pos), (arg_t, pos)), pos), acc), pos))
|
||||
scope_sig result_typ
|
||||
|
||||
let translate_program (prgm : Ast.program) (top_level_scope_name : Ast.ScopeName.t) :
|
||||
Dcalc.Ast.expr Pos.marked =
|
||||
let scope_dependencies = Dependency.build_program_dep_graph prgm in
|
||||
Dependency.check_for_cycle_in_scope scope_dependencies;
|
||||
Dependency.check_type_cycles prgm.program_structs prgm.program_enums;
|
||||
let scope_ordering = Dependency.get_scope_ordering scope_dependencies in
|
||||
let struct_ctx = prgm.program_structs in
|
||||
let enum_ctx = prgm.program_enums in
|
||||
let sctx : scope_sigs_ctx =
|
||||
Ast.ScopeMap.mapi
|
||||
(fun scope_name scope ->
|
||||
let scope_dvar = Dcalc.Ast.Var.make (Ast.ScopeName.get_info scope.Ast.scope_decl_name) in
|
||||
( List.map
|
||||
(fun (scope_var, tau) ->
|
||||
let tau =
|
||||
translate_typ (empty_ctx struct_ctx enum_ctx Ast.ScopeMap.empty scope_name) tau
|
||||
in
|
||||
(scope_var, Pos.unmark tau))
|
||||
(Ast.ScopeVarMap.bindings scope.scope_sig),
|
||||
scope_dvar ))
|
||||
prgm.program_scopes
|
||||
in
|
||||
(* the final expression on which we build on is the variable of the top-level scope that we are
|
||||
returning *)
|
||||
let acc = Dcalc.Ast.make_var (snd (Ast.ScopeMap.find top_level_scope_name sctx), Pos.no_pos) in
|
||||
(* the resulting expression is the list of definitions of all the scopes, ending with the
|
||||
top-level scope. *)
|
||||
Bindlib.unbox
|
||||
(let acc =
|
||||
List.fold_right
|
||||
(fun scope_name (acc : Dcalc.Ast.expr Pos.marked Bindlib.box) ->
|
||||
let scope = Ast.ScopeMap.find scope_name prgm.program_scopes in
|
||||
let pos_scope = Pos.get_position (Ast.ScopeName.get_info scope.scope_decl_name) in
|
||||
let scope_expr = translate_scope_decl struct_ctx enum_ctx sctx scope_name scope in
|
||||
let scope_sig, dvar = Ast.ScopeMap.find scope_name sctx in
|
||||
let scope_typ = build_scope_typ_from_sig scope_sig pos_scope in
|
||||
Dcalc.Ast.make_let_in dvar scope_typ scope_expr acc)
|
||||
scope_ordering acc
|
||||
in
|
||||
acc)
|
38
src/catala/scope_language/scopelang.mld
Normal file
@ -0,0 +1,38 @@
|
||||
{0 The scope language }
|
||||
|
||||
This representation is the third in the compilation chain
|
||||
(see {{: index.html#architecture} Architecture}). Its main difference
|
||||
with the previous {{: desugared.html} desugared representation} is that inside
|
||||
a scope, the definitions are ordered according to their computational
|
||||
dependency order, and each definition is a {!constructor: Dcalc.Ast.EDefault} tree
|
||||
instead of a flat list of rules.
|
||||
|
||||
The module describing the abstract syntax tree is:
|
||||
|
||||
{!modules: Scopelang.Ast}
|
||||
|
||||
Printing helpers can be found in {!module: Scopelang.Print}.
|
||||
|
||||
This intermediate representation corresponds to the scope language
|
||||
presented in the {{: https://github.com/CatalaLang/catala/raw/master/doc/formalization/formalization.pdf}
|
||||
Catala formalization}.
|
||||
|
||||
{1 Translation to the default calculus}
|
||||
|
||||
Related modules:
|
||||
|
||||
{!modules: Scopelang.Dependency Scopelang.Scope_to_dcalc}
|
||||
|
||||
The translation from the scope language to the
|
||||
{{: dcalc.html} default calculus} involves three big features:
|
||||
|
||||
{ol
|
||||
{li Translating structures and enums into simpler sum and product types}
|
||||
{li Build thunked signatures for the scopes as functions }
|
||||
{li Transform the list of scopes into a program}
|
||||
}
|
||||
|
||||
1 and 3 involve computing dependency graphs for respectively the structs and
|
||||
enums on one hand, and the inter-scope dependencies on the other hand. Both
|
||||
can be found in {!module: Scopelang.Dependency}, while
|
||||
{!module: Scopelang.Scope_to_dcalc} is mostly responsible for 2.
|
@ -1,98 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module IdentMap = Map.Make (String)
|
||||
|
||||
module type Id = sig
|
||||
type t
|
||||
|
||||
type info
|
||||
|
||||
val fresh : info -> t
|
||||
|
||||
val get_info : t -> info
|
||||
|
||||
val compare : t -> t -> int
|
||||
|
||||
val format_t : t -> string
|
||||
|
||||
val hash : t -> int
|
||||
end
|
||||
|
||||
module Make (X : sig
|
||||
type info
|
||||
|
||||
val format_info : info -> string
|
||||
end) : Id with type info = X.info = struct
|
||||
type t = { id : int; info : X.info }
|
||||
|
||||
type info = X.info
|
||||
|
||||
let counter = ref 0
|
||||
|
||||
let fresh (info : X.info) : t =
|
||||
incr counter;
|
||||
{ id = !counter; info }
|
||||
|
||||
let get_info (uid : t) : X.info = uid.info
|
||||
|
||||
let compare (x : t) (y : t) : int = compare x.id y.id
|
||||
|
||||
let format_t (x : t) : string = Printf.sprintf "%s" (X.format_info x.info)
|
||||
|
||||
let hash (x : t) : int = x.id
|
||||
end
|
||||
|
||||
module MarkedString = struct
|
||||
type info = string Pos.marked
|
||||
|
||||
let format_info (s, _) = s
|
||||
end
|
||||
|
||||
module Scope = Make (MarkedString)
|
||||
module ScopeSet = Set.Make (Scope)
|
||||
module ScopeMap = Map.Make (Scope)
|
||||
module Var = Make (MarkedString)
|
||||
module VarSet = Set.Make (Var)
|
||||
module VarMap = Map.Make (Var)
|
||||
module LocalVar = Make (MarkedString)
|
||||
module LocalVarSet = Set.Make (LocalVar)
|
||||
module LocalVarMap = Map.Make (LocalVar)
|
||||
module SubScope = Make (MarkedString)
|
||||
module SubScopeSet = Set.Make (SubScope)
|
||||
module SubScopeMap = Map.Make (SubScope)
|
||||
|
||||
(** Inside a scope, a definition can refer either to a scope def, or a subscope def *)
|
||||
module ScopeDef = struct
|
||||
type t =
|
||||
| Var of Var.t
|
||||
| SubScopeVar of SubScope.t * Var.t
|
||||
(** In this case, the [Uid.Var.t] lives inside the context of the subscope's original
|
||||
declaration *)
|
||||
|
||||
let compare x y =
|
||||
match (x, y) with
|
||||
| Var x, Var y | Var x, SubScopeVar (_, y) | SubScopeVar (_, x), Var y -> Var.compare x y
|
||||
| SubScopeVar (_, x), SubScopeVar (_, y) -> SubScope.compare x y
|
||||
|
||||
let format_t x =
|
||||
match x with
|
||||
| Var v -> Var.format_t v
|
||||
| SubScopeVar (s, v) -> Printf.sprintf "%s.%s" (SubScope.format_t s) (Var.format_t v)
|
||||
|
||||
let hash x = match x with Var v -> Var.hash v | SubScopeVar (_, v) -> Var.hash v
|
||||
end
|
||||
|
||||
module ScopeDefMap = Map.Make (ScopeDef)
|
||||
module ScopeDefSet = Set.Make (ScopeDef)
|
@ -1,79 +0,0 @@
|
||||
(* This file is part of the Catala compiler, a specification language for tax and social benefits
|
||||
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
|
||||
<denis.merigoux@inria.fr>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing permissions and limitations under
|
||||
the License. *)
|
||||
|
||||
module IdentMap : Map.S with type key = String.t
|
||||
|
||||
module MarkedString : sig
|
||||
type info = string Pos.marked
|
||||
|
||||
val format_info : 'a * 'b -> 'a
|
||||
end
|
||||
|
||||
module type Id = sig
|
||||
type t
|
||||
|
||||
type info
|
||||
|
||||
val fresh : info -> t
|
||||
|
||||
val get_info : t -> info
|
||||
|
||||
val compare : t -> t -> int
|
||||
|
||||
val format_t : t -> string
|
||||
|
||||
val hash : t -> int
|
||||
end
|
||||
|
||||
module Scope : Id with type info = MarkedString.info
|
||||
|
||||
module ScopeSet : Set.S with type elt = Scope.t
|
||||
|
||||
module ScopeMap : Map.S with type key = Scope.t
|
||||
|
||||
module Var : Id with type info = MarkedString.info
|
||||
|
||||
module VarSet : Set.S with type elt = Var.t
|
||||
|
||||
module VarMap : Map.S with type key = Var.t
|
||||
|
||||
module LocalVar : Id with type info = MarkedString.info
|
||||
|
||||
module LocalVarSet : Set.S with type elt = LocalVar.t
|
||||
|
||||
module LocalVarMap : Map.S with type key = LocalVar.t
|
||||
|
||||
module SubScope : Id with type info = MarkedString.info
|
||||
|
||||
module SubScopeSet : Set.S with type elt = SubScope.t
|
||||
|
||||
module SubScopeMap : Map.S with type key = SubScope.t
|
||||
|
||||
module ScopeDef : sig
|
||||
type t =
|
||||
| Var of Var.t
|
||||
| SubScopeVar of SubScope.t * Var.t
|
||||
(** In this case, the [Uid.Var.t] lives inside the context of the subscope's original
|
||||
declaration *)
|
||||
|
||||
val compare : t -> t -> int
|
||||
|
||||
val format_t : t -> string
|
||||
|
||||
val hash : t -> int
|
||||
end
|
||||
|
||||
module ScopeDefMap : Map.S with type key = ScopeDef.t
|
||||
|
||||
module ScopeDefSet : Set.S with type elt = ScopeDef.t
|
@ -15,12 +15,19 @@
|
||||
(** Ssource files to be compiled *)
|
||||
let source_files : string list ref = ref []
|
||||
|
||||
let contents : string ref = ref ""
|
||||
|
||||
(** Prints debug information *)
|
||||
let debug_flag = ref false
|
||||
|
||||
(* Styles the terminal output *)
|
||||
let style_flag = ref true
|
||||
|
||||
(* Max number of digits to show for decimal results *)
|
||||
let max_prec_digits = ref 20
|
||||
|
||||
let trace_flag = ref false
|
||||
|
||||
open Cmdliner
|
||||
|
||||
let file =
|
||||
@ -33,6 +40,9 @@ let debug = Arg.(value & flag & info [ "debug"; "d" ] ~doc:"Prints debug informa
|
||||
|
||||
let unstyled = Arg.(value & flag & info [ "unstyled" ] ~doc:"Removes styling from terminal output")
|
||||
|
||||
let trace_opt =
|
||||
Arg.(value & flag & info [ "trace"; "t" ] ~doc:"Displays a trace of the intepreter's computation")
|
||||
|
||||
let wrap_weaved_output =
|
||||
Arg.(
|
||||
value & flag
|
||||
@ -53,6 +63,13 @@ let language =
|
||||
& info [ "l"; "language" ] ~docv:"LANG"
|
||||
~doc:"Input language among: en, fr, non-verbose (default non-verbose)")
|
||||
|
||||
let max_prec_digits_opt =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some int) None
|
||||
& info [ "p"; "max_digits_printed" ] ~docv:"LANG"
|
||||
~doc:"Maximum number of significant digits printed for decimal results (default 20)")
|
||||
|
||||
let ex_scope =
|
||||
Arg.(
|
||||
value & opt (some string) None & info [ "s"; "scope" ] ~docv:"SCOPE" ~doc:"Scope to be executed")
|
||||
@ -76,13 +93,15 @@ let pygmentize_loc =
|
||||
Arg.(
|
||||
value
|
||||
& opt (some string) None
|
||||
& info [ "pygmentize"; "p" ] ~docv:"PYGMENTIZE"
|
||||
& info [ "pygmentize" ] ~docv:"PYGMENTIZE"
|
||||
~doc:"Location of a custom pygmentize executable for LaTeX source code highlighting")
|
||||
|
||||
let catala_t f =
|
||||
Term.(
|
||||
const f $ file $ debug $ unstyled $ wrap_weaved_output $ pygmentize_loc $ backend $ language
|
||||
$ ex_scope $ output)
|
||||
$ max_prec_digits_opt $ trace_opt $ ex_scope $ output)
|
||||
|
||||
let version = "0.2.0"
|
||||
|
||||
let info =
|
||||
let doc =
|
||||
@ -96,20 +115,16 @@ let info =
|
||||
from legislative texts.";
|
||||
`S Manpage.s_authors;
|
||||
`P "Denis Merigoux <denis.merigoux@inria.fr>";
|
||||
`P "Nicolas Chataing <nicolas.chataing@ens.fr>";
|
||||
`S Manpage.s_examples;
|
||||
`P "Typical usage:";
|
||||
`Pre "catala LaTeX file.catala";
|
||||
`S Manpage.s_bugs;
|
||||
`P "Please file bug reports at https://gitlab.inria.fr/verifisc/catala/issues";
|
||||
`P "Please file bug reports at https://github.com/CatalaLang/catala/issues";
|
||||
]
|
||||
in
|
||||
let exits = Term.default_exits @ [ Term.exit_info ~doc:"on error." 1 ] in
|
||||
Term.info "catala"
|
||||
~version:
|
||||
( match Build_info.V1.version () with
|
||||
| None -> "n/a"
|
||||
| Some v -> Build_info.V1.Version.to_string v )
|
||||
~doc ~exits ~man
|
||||
Term.info "catala" ~version ~doc ~exits ~man
|
||||
|
||||
(**{1 Terminal formatting}*)
|
||||
|
||||
@ -130,6 +145,9 @@ let warning_marker () = print_with_style [ ANSITerminal.Bold; ANSITerminal.yello
|
||||
(** Prints [\[RESULT\]] in green on the terminal standard output *)
|
||||
let result_marker () = print_with_style [ ANSITerminal.Bold; ANSITerminal.green ] "[RESULT] "
|
||||
|
||||
(** Prints [\[LOG\]] in red on the terminal error output *)
|
||||
let log_marker () = print_with_style [ ANSITerminal.Bold; ANSITerminal.black ] "[LOG] "
|
||||
|
||||
(**{2 Printers}*)
|
||||
|
||||
(** All the printers below print their argument after the correct marker *)
|
||||
@ -176,3 +194,8 @@ let result_print (s : string) =
|
||||
Printf.printf "%s\n" (add_prefix_to_each_line s (fun _ -> result_marker ()));
|
||||
flush stdout;
|
||||
flush stdout
|
||||
|
||||
let log_print (s : string) =
|
||||
Printf.printf "%s\n" (add_prefix_to_each_line s (fun _ -> log_marker ()));
|
||||
flush stdout;
|
||||
flush stdout
|