mirror of
https://github.com/CatalaLang/catala.git
synced 2024-11-08 07:51:43 +03:00
feat(runtimes): add rescript-catala
This commit is contained in:
parent
c5124f50c4
commit
2a39f1b5f8
3
runtimes/rescript/.gitignore
vendored
Normal file
3
runtimes/rescript/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
lib
|
||||
*.bs.js
|
26
runtimes/rescript/README.md
Normal file
26
runtimes/rescript/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# `rescript-catala`
|
||||
|
||||
A ReScript wrapper for the Catala runtime
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
yarn add @catala-lang/rescript-catala
|
||||
```
|
||||
|
||||
## Local dev
|
||||
|
||||
```
|
||||
# in this dir
|
||||
yarn
|
||||
yarn build
|
||||
yarn link
|
||||
|
||||
# in the project to use the lib
|
||||
yarn link @catala-lang/rescript-catala
|
||||
```
|
||||
|
||||
## Example of use
|
||||
|
||||
* [catala-website](https://github.com/CatalaLang/catala-website)
|
||||
* [catala-explain](https://github.com/CatalaLang/catala-explain)
|
24
runtimes/rescript/bsconfig.json
Normal file
24
runtimes/rescript/bsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@catala-lang/rescript-catala",
|
||||
"sources": [
|
||||
{
|
||||
"dir": "src",
|
||||
"subdir": true
|
||||
}
|
||||
],
|
||||
"package-specs": [
|
||||
{
|
||||
"module": "es6",
|
||||
"in-source": true
|
||||
}
|
||||
],
|
||||
"suffix": ".bs.js",
|
||||
"refmt": 3,
|
||||
"jsx": { "version": 4, "mode": "classic" },
|
||||
"bs-dependencies": [
|
||||
"decco"
|
||||
],
|
||||
"ppx-flags": [
|
||||
"decco/ppx"
|
||||
]
|
||||
}
|
27
runtimes/rescript/package.json
Normal file
27
runtimes/rescript/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@catala-lang/rescript-catala",
|
||||
"version": "0.1.0",
|
||||
"description": "ReScript wrapper for the Catala runtime",
|
||||
"scripts": {
|
||||
"clean": "rescript clean",
|
||||
"build": "rescript build -with-deps",
|
||||
"watch": "yarn run build -w"
|
||||
},
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:CatalaLang/catala-explain.git",
|
||||
"author": "Emile Rolley <emile.rolley@tuta.io>",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/CatalaLang/catala-explain/issues"
|
||||
},
|
||||
"homepage": "https://github.com/CatalaLang/catala/tree/master/runtimes/rescript/README.md",
|
||||
"keywords": [
|
||||
"ReScript",
|
||||
"catala",
|
||||
"runtime"
|
||||
],
|
||||
"devDependencies": {
|
||||
"decco": "^1.6.0",
|
||||
"rescript": "^10.1.4"
|
||||
}
|
||||
}
|
176
runtimes/rescript/src/Runtime.res
Normal file
176
runtimes/rescript/src/Runtime.res
Normal file
@ -0,0 +1,176 @@
|
||||
@decco.decode
|
||||
type sourcePosition = {
|
||||
filename: string,
|
||||
start_line: int,
|
||||
end_line: int,
|
||||
start_column: int,
|
||||
end_column: int,
|
||||
law_headings: array<string>,
|
||||
}
|
||||
|
||||
module rec LoggedValue: {
|
||||
@decco.decode
|
||||
type rec t =
|
||||
| Unit
|
||||
| Bool(bool)
|
||||
| Integer(int)
|
||||
| Money(float)
|
||||
| Decimal(float)
|
||||
| Date(string)
|
||||
| Duration(string)
|
||||
| Enum(list<string>, (string, t))
|
||||
| Struct(list<string>, list<(string, t)>)
|
||||
| Array(array<t>)
|
||||
| Unembeddable
|
||||
|
||||
let loggedValueToString: (t, int) => string
|
||||
} = {
|
||||
@decco.decode
|
||||
type rec t =
|
||||
| Unit
|
||||
| Bool(bool)
|
||||
| Integer(int)
|
||||
| Money(float)
|
||||
| Decimal(float)
|
||||
| Date(string)
|
||||
| Duration(string)
|
||||
| Enum(list<string>, (string, t))
|
||||
| Struct(list<string>, list<(string, t)>)
|
||||
| Array(array<t>)
|
||||
| Unembeddable
|
||||
|
||||
let rec loggedValueToString = (val: t, tab: int) => {
|
||||
Js.String.repeat(tab, "\t") ++
|
||||
switch val {
|
||||
| Unit => "Unit"
|
||||
| Bool(b) => "Bool: " ++ string_of_bool(b)
|
||||
| Money(f) => "Money: " ++ Js.Float.toString(f)
|
||||
| Integer(i) => "Integer: " ++ string_of_int(i)
|
||||
| Decimal(f) => "Decimal: " ++ Js.Float.toString(f)
|
||||
| Date(d) => "Date: " ++ d
|
||||
| Duration(d) => "Duration: " ++ d
|
||||
| Enum(ls, (s, vals)) =>
|
||||
"Enum[" ++ String.concat(",", ls) ++ "]:" ++ s ++ "\n" ++ vals->loggedValueToString(tab + 1)
|
||||
| _ => "Other"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ocaml.doc("The logging is constituted of two phases:
|
||||
|
||||
- The first one consists of collecting {i raw} events (see
|
||||
{!type:raw_event}) during the program execution (see {!val:retrieve_log})
|
||||
throught {!instruments}.
|
||||
- The second one consists in parsing the collected raw events into
|
||||
{i structured} ones (see {!type: event}).")
|
||||
module Raw = {
|
||||
type eventSerialized = {
|
||||
eventType: string,
|
||||
information: array<string>,
|
||||
sourcePosition: Js.Nullable.t<sourcePosition>,
|
||||
loggedValueJson: string,
|
||||
}
|
||||
|
||||
type eventType =
|
||||
| BeginCall
|
||||
| EndCall
|
||||
| VariableDefinition
|
||||
| DecisionTaken
|
||||
|
||||
type event = {
|
||||
eventType: eventType,
|
||||
information: array<string>,
|
||||
sourcePosition: option<sourcePosition>,
|
||||
loggedValue: LoggedValue.t,
|
||||
}
|
||||
|
||||
let eventTypeFromString = (str: string): eventType => {
|
||||
switch str {
|
||||
| "Begin call" => BeginCall
|
||||
| "End call" => EndCall
|
||||
| "Variable definition" => VariableDefinition
|
||||
| "Decision taken" => DecisionTaken
|
||||
| _ => Js.Exn.raiseError(`Unknown event type: ${str}`)
|
||||
}
|
||||
}
|
||||
|
||||
let deserializedEvents = (rawEventsSerialized: array<eventSerialized>) => {
|
||||
rawEventsSerialized->Belt.Array.map((rawEventSerialized: eventSerialized) => {
|
||||
let loggedValue = try {
|
||||
switch LoggedValue.t_decode(Js.Json.parseExn(rawEventSerialized.loggedValueJson)) {
|
||||
| Ok(val) => val
|
||||
| Error(_decodeError) => LoggedValue.Unembeddable
|
||||
}
|
||||
} catch {
|
||||
| Js.Exn.Error(obj) =>
|
||||
switch Js.Exn.message(obj) {
|
||||
| Some(m) =>
|
||||
Js.log("Caught a JS exception! Message: " ++ m)
|
||||
LoggedValue.Unembeddable
|
||||
| None => LoggedValue.Unembeddable
|
||||
}
|
||||
}
|
||||
{
|
||||
eventType: rawEventSerialized.eventType->eventTypeFromString,
|
||||
information: rawEventSerialized.information,
|
||||
sourcePosition: rawEventSerialized.sourcePosition->Js.Nullable.toOption,
|
||||
loggedValue,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type eventSerialized = {data: string}
|
||||
|
||||
@decco.decode
|
||||
type information = list<string>
|
||||
|
||||
@decco.decode
|
||||
type rec event =
|
||||
| VarComputation(var_def)
|
||||
| FunCall(fun_call)
|
||||
| SubScopeCall(sub_scope_call)
|
||||
@decco.decode
|
||||
and var_def = {
|
||||
pos: option<sourcePosition>,
|
||||
name: information,
|
||||
value: LoggedValue.t,
|
||||
fun_calls: option<list<fun_call>>,
|
||||
}
|
||||
@decco.decode
|
||||
and fun_call = {
|
||||
fun_name: information,
|
||||
fun_inputs: list<var_def>,
|
||||
body: list<event>,
|
||||
output: var_def,
|
||||
}
|
||||
@decco.decode
|
||||
and sub_scope_call = {
|
||||
@decco.key("name") sname: information,
|
||||
inputs: list<var_def>,
|
||||
@decco.key("body") sbody: list<event>,
|
||||
}
|
||||
|
||||
let deserializedEvents = (eventsSerialized: array<eventSerialized>) => {
|
||||
eventsSerialized->Belt.Array.map((eventSerialized: eventSerialized) => {
|
||||
let event = try {
|
||||
switch event_decode(Js.Json.parseExn(eventSerialized.data)) {
|
||||
| Ok(val) => val
|
||||
| Error(decodeError) =>
|
||||
Js.Exn.raiseError(
|
||||
"Error while decoding serialized events at " ++
|
||||
decodeError.path ++
|
||||
": " ++
|
||||
decodeError.message,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
| Js.Exn.Error(exn) =>
|
||||
switch exn->Js.Exn.message {
|
||||
| Some(msg) => Js.Exn.raiseError("Error while parsing event: " ++ msg)
|
||||
| None => Js.Exn.raiseError("Error while parsing event.")
|
||||
}
|
||||
}
|
||||
event
|
||||
})
|
||||
}
|
133
runtimes/rescript/src/Runtime.resi
Normal file
133
runtimes/rescript/src/Runtime.resi
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
This module allows to deserialize log events from the [french_law] web API.
|
||||
|
||||
NOTE: Following types are deserialized with the ReScript lib [decco] which
|
||||
seems to have the same encoding that [ppx_yojson_conv], the one used in the
|
||||
OCaml side to serialize them.
|
||||
But there is no guarantee that the encoding will continue to be the same.
|
||||
|
||||
@see https://github.com/CatalaLang/catala/tree/master/french_law/ocaml#wrappers and
|
||||
@see https://github.com/CatalaLang/catala/blob/master/compiler/runtime.mli#L26-L185
|
||||
@see https://github.com/reasonml-labs/decco
|
||||
@see https://github.com/janestreet/ppx_yojson_conv
|
||||
*/
|
||||
|
||||
/* Position of an event in a Catala file. */
|
||||
@decco.decode
|
||||
type sourcePosition = {
|
||||
filename: string,
|
||||
start_line: int,
|
||||
end_line: int,
|
||||
start_column: int,
|
||||
end_column: int,
|
||||
law_headings: array<string>,
|
||||
}
|
||||
|
||||
module LoggedValue: {
|
||||
/* Catala runtime value. */
|
||||
@decco.decode
|
||||
type rec t =
|
||||
| Unit
|
||||
| Bool(bool)
|
||||
| Integer(int)
|
||||
| Money(float)
|
||||
| Decimal(float)
|
||||
| Date(string)
|
||||
| Duration(string)
|
||||
// The enum type followed the name of the enum constructor and its value.
|
||||
| Enum(list<string>, (string, t))
|
||||
// The struct type followed the list of its fields.
|
||||
| Struct(list<string>, list<(string, t)>)
|
||||
| Array(array<t>)
|
||||
// Functions -- which are ignored.
|
||||
| Unembeddable
|
||||
|
||||
/*
|
||||
[loggedValueToString(val, tab)] returns the string representation of [val]
|
||||
indented of [tab] tabulations.
|
||||
*/
|
||||
let loggedValueToString: (t, int) => string
|
||||
}
|
||||
|
||||
/*
|
||||
There is two kinds of log events:
|
||||
- _raw_ ones are the events that are directly received from the web API.
|
||||
- _structured_ ones are the events built from the raw ones.
|
||||
*/
|
||||
|
||||
module Raw: {
|
||||
/* Serialized raw event received directly from the [french_law] web API. */
|
||||
type eventSerialized = {
|
||||
eventType: string,
|
||||
information: array<string>,
|
||||
sourcePosition: Js.Nullable.t<sourcePosition>,
|
||||
loggedValueJson: string,
|
||||
}
|
||||
|
||||
type eventType =
|
||||
| BeginCall
|
||||
| EndCall
|
||||
| VariableDefinition
|
||||
| DecisionTaken
|
||||
|
||||
/* Deserialized raw event. */
|
||||
type event = {
|
||||
eventType: eventType,
|
||||
information: array<string>,
|
||||
sourcePosition: option<sourcePosition>,
|
||||
loggedValue: LoggedValue.t,
|
||||
}
|
||||
|
||||
let deserializedEvents: array<eventSerialized> => array<event>
|
||||
}
|
||||
|
||||
/* Serialized log event received directly from the [french_law] web API, in a [data] payload. */
|
||||
type eventSerialized = {data: string}
|
||||
|
||||
/*
|
||||
Represents information about a name in the code -- i.e. variable name,
|
||||
subscope name, etc...
|
||||
|
||||
It's a list of strings with a length varying from 2 to 3, where:
|
||||
|
||||
- the first string is the name of the current scope -- starting with a
|
||||
capitalized letter [Scope_name],
|
||||
- the second string is either: the name of a scope variable or, the name of
|
||||
a subscope input variable -- [a_subscope_var.input_var]
|
||||
- the third string is either: a subscope name (starting with a capitalized
|
||||
letter [Subscope_name] or, the [input] (resp. [output]) string -- which
|
||||
corresponds to the input (resp. the output) of a function.
|
||||
*/
|
||||
@decco.decode
|
||||
type information = list<string>
|
||||
|
||||
/* Deserialized log event. */
|
||||
@decco.decode
|
||||
type rec event =
|
||||
| VarComputation(var_def)
|
||||
| FunCall(fun_call)
|
||||
| SubScopeCall(sub_scope_call)
|
||||
@decco.decode
|
||||
and var_def = {
|
||||
pos: option<sourcePosition>,
|
||||
name: information,
|
||||
value: LoggedValue.t,
|
||||
fun_calls: option<list<fun_call>>,
|
||||
}
|
||||
/* For decco.decode to work, these type declaration have to match *exactly* those
|
||||
in catala/runtimes/ocaml/runtime.mli */
|
||||
@decco.decode
|
||||
and fun_call = {
|
||||
fun_name: information,
|
||||
fun_inputs: list<var_def>,
|
||||
body: list<event>,
|
||||
output: var_def,
|
||||
}
|
||||
@decco.decode
|
||||
and sub_scope_call = {
|
||||
@decco.key("name") sname: information,
|
||||
inputs: list<var_def>,
|
||||
@decco.key("body") sbody: list<event>,
|
||||
}
|
||||
|
||||
let deserializedEvents: array<eventSerialized> => array<event>
|
13
runtimes/rescript/yarn.lock
Normal file
13
runtimes/rescript/yarn.lock
Normal file
@ -0,0 +1,13 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
decco@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/decco/-/decco-1.6.0.tgz#79a07d518691f92519066a067b77f04ce89b4896"
|
||||
integrity sha512-gdeDDPOh45Hz8YGvTkDP7ySo3Ll3ty+KfuFj21+jRbiCoE8HTCNB++pozCiMljxJx39CfvrHRYBY5FO5PMyXzw==
|
||||
|
||||
rescript@^10.1.4:
|
||||
version "10.1.4"
|
||||
resolved "https://registry.yarnpkg.com/rescript/-/rescript-10.1.4.tgz#0f37710d371f32a704f17b4e804f66ce3c79a305"
|
||||
integrity sha512-FFKlS9AG/XrLepWsyw7B+A9DtQBPWEPDPDKghV831Y2KGbie+eeFBOS0xtRHp0xbt7S0N2Dm6hhX+kTZQ/3Ybg==
|
Loading…
Reference in New Issue
Block a user