feat(runtimes): add rescript-catala

This commit is contained in:
Emile Rolley 2023-05-05 11:06:20 +02:00 committed by Emile Rolley
parent c5124f50c4
commit 2a39f1b5f8
7 changed files with 402 additions and 0 deletions

3
runtimes/rescript/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
lib
*.bs.js

View 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)

View 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"
]
}

View 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"
}
}

View 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
})
}

View 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>

View 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==