├── fastpack.json ├── doc ├── array.png ├── diffB.png ├── union.png ├── diffA1A2.png ├── inference.png ├── singleton.png ├── TypeCheckingTypes.md ├── Erasure.md ├── SumOperations.md ├── TypeCheckingStyp.md ├── Abduction.md ├── UnionExtension.md └── Diff.md ├── .gitignore ├── src ├── Component1.re ├── Serialize.rei ├── Component1.js ├── Index.re ├── Index.js ├── Samples.rei ├── Samples.re ├── Serialize.re ├── DynTypedJson.re ├── Serialize.js ├── Samples.js ├── Demo.re ├── TypeCheck.re ├── Styp.re ├── DynTypedJson.js ├── Demo.js ├── UI.re ├── Diff.re ├── Styp.js ├── TypeCheck.js ├── UI.js └── Diff.js ├── webpack.config.js ├── bsconfig.json ├── index-parcel.html ├── index-rollup.html ├── index-fastpack.html ├── index-webpack.html ├── react-treeview.css ├── rollup.config.browser.js ├── LICENSE.md ├── opinionated.css ├── package.json └── README.md /fastpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/Index.js"] 3 | } -------------------------------------------------------------------------------- /doc/array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/array.png -------------------------------------------------------------------------------- /doc/diffB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/diffB.png -------------------------------------------------------------------------------- /doc/union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/union.png -------------------------------------------------------------------------------- /doc/diffA1A2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/diffA1A2.png -------------------------------------------------------------------------------- /doc/inference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/inference.png -------------------------------------------------------------------------------- /doc/singleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristianoc/REInfer/HEAD/doc/singleton.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /yarn.lock 3 | /src/*.bs.js 4 | /.merlin 5 | /lib 6 | /.bsb.lock 7 | /.cache 8 | /bundledOutputs 9 | -------------------------------------------------------------------------------- /src/Component1.re: -------------------------------------------------------------------------------- 1 | [@react.component] 2 | let make = (~message) => { 3 |
Js.log("clicked!")}> 4 | {React.string(message)} 5 |
; 6 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: { 5 | index: "./src/Index.js" 6 | }, 7 | mode: "production", 8 | output: { 9 | path: path.join(__dirname, "bundledOutputs"), 10 | filename: "webpack.js" 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/Serialize.rei: -------------------------------------------------------------------------------- 1 | /** 2 | * Serializer for arbitrary values. 3 | * Note: might not work across versions of the compiler (as the runtime representation might change). 4 | * Note: uses json stringify under the hood: does not support functions or cyclic values. 5 | */ 6 | 7 | let toString: 'a => string; 8 | 9 | let fromString: string => 'a; -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-template", 3 | "reason": { 4 | "react-jsx": 3 5 | }, 6 | "sources": { 7 | "dir" : "src", 8 | "subdirs" : true 9 | }, 10 | "package-specs": [{ 11 | "module": "es6", 12 | "in-source": true 13 | }], 14 | "suffix": ".js", 15 | "namespace": true, 16 | "bs-dependencies": [ 17 | "reason-react" 18 | ], 19 | "refmt": 3 20 | } 21 | -------------------------------------------------------------------------------- /doc/TypeCheckingTypes.md: -------------------------------------------------------------------------------- 1 | ``` 2 | |- 123 : number 3 | ``` 4 | 5 | ``` 6 | |- “abc” : string 7 | ``` 8 | 9 | ``` 10 | |- true : boolean |- false : boolean 11 | ``` 12 | 13 | ``` 14 | |- null : t? 15 | ``` 16 | 17 | ``` 18 | |- val1:t1 … |- valn:tn 19 | ———————————————————————————————————————————————— 20 | |- {x1:val1, …, xn:valn} : {x1:t1, …, xn:tn} 21 | ``` 22 | 23 | ``` 24 | |- [] : [empty] 25 | ``` 26 | 27 | ``` 28 | |- val1:t … |- valn:t (n>0) 29 | —————————————————————————————————— 30 | |- [val1, …, valn] : [t] 31 | ``` 32 | -------------------------------------------------------------------------------- /src/Component1.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as React from "react"; 4 | 5 | function Component1(Props) { 6 | var message = Props.message; 7 | return React.createElement("div", { 8 | onClick: (function (_event) { 9 | console.log("clicked!"); 10 | return /* () */0; 11 | }) 12 | }, message); 13 | } 14 | 15 | var make = Component1; 16 | 17 | export { 18 | make , 19 | 20 | } 21 | /* react Not a pure module */ 22 | -------------------------------------------------------------------------------- /doc/Erasure.md: -------------------------------------------------------------------------------- 1 | ``` 2 | |- |typ| = t 3 | ——————————————————— 4 | |- |(typ,o)::p| = t 5 | ``` 6 | 7 | ``` 8 | |- |empty| = empty 9 | ``` 10 | 11 | ``` 12 | |- |number| = number 13 | ``` 14 | 15 | ``` 16 | |- |string| = string 17 | ``` 18 | 19 | ``` 20 | |- |boolean| = boolean 21 | ``` 22 | 23 | ``` 24 | |- |styp1| = t1 … |- |stypn| = tn 25 | ———————————————————————————————————————————————————— 26 | |- |{x1:styp1, …, xn:stypn}| = {x1:t1, …, xn:tn} 27 | ``` 28 | 29 | ``` 30 | |- |styp| = t 31 | ——————————————————— 32 | |- |[styp]| = [t] 33 | ``` 34 | -------------------------------------------------------------------------------- /src/Index.re: -------------------------------------------------------------------------------- 1 | module Main = { 2 | [@react.component] 3 | let make = () => { 4 | let diffs = Demo.test(); 5 |
6 | {diffs 7 | ->Belt.List.toArray 8 | ->( 9 | Belt.Array.mapWithIndex((i, diff) => 10 |
string_of_int}> 11 |

{React.string("Sample " ++ string_of_int(i))}

12 | 13 |
14 | ) 15 | ) 16 | ->React.array} 17 |
; 18 | }; 19 | }; 20 | 21 | ReactDOMRe.renderToElementWithId(
, "main"); -------------------------------------------------------------------------------- /index-parcel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | REInfer: Runtime Extended Inference 6 | 7 | 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /index-rollup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | REInfer: Runtime Extended Inference 6 | 7 | 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /index-fastpack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | REInfer: Runtime Extended Inference 6 | 7 | 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /index-webpack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | REInfer: Runtime Extended Inference 6 | 7 | 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /react-treeview.css: -------------------------------------------------------------------------------- 1 | /* the tree node's style */ 2 | .tree-view { 3 | overflow-y: hidden; 4 | } 5 | 6 | .tree-view_item { 7 | /* immediate child of .tree-view, for styling convenience */ 8 | } 9 | 10 | /* style for the children nodes container */ 11 | .tree-view_children { 12 | margin-left: 16px; 13 | } 14 | 15 | .tree-view_children-collapsed { 16 | height: 0px; 17 | } 18 | 19 | .tree-view_arrow { 20 | cursor: pointer; 21 | margin-right: 6px; 22 | display: inline-block; 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | -ms-user-select: none; 26 | user-select: none; 27 | } 28 | 29 | .tree-view_arrow:after { 30 | content: '▾'; 31 | } 32 | 33 | /* rotate the triangle to close it */ 34 | .tree-view_arrow-collapsed { 35 | -webkit-transform: rotate(-90deg); 36 | -moz-transform: rotate(-90deg); 37 | -ms-transform: rotate(-90deg); 38 | transform: rotate(-90deg); 39 | } 40 | -------------------------------------------------------------------------------- /rollup.config.browser.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-node-resolve"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import replace from "rollup-plugin-replace"; 4 | import { uglify } from "rollup-plugin-uglify"; 5 | 6 | export default { 7 | input: "src/Index.js", 8 | output: { 9 | file: "bundledOutputs/rollup.js", 10 | format: "iife", 11 | name: "main" 12 | }, 13 | plugins: [ 14 | resolve(), 15 | commonjs({ 16 | include: "node_modules/**", 17 | namedExports: { 18 | // left-hand side can be an absolute path, a path 19 | // relative to the current directory, or the name 20 | // of a module in node_modules 21 | "react-dom": ["render", "hydrate"], 22 | react: ["createElement", "useReducer"] 23 | } 24 | }), 25 | replace({ 26 | "process.env.NODE_ENV": "'production'" 27 | }), 28 | uglify() 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /doc/SumOperations.md: -------------------------------------------------------------------------------- 1 | ``` 2 | |- typ1+typ2=typ |-o1+o2=o p1+p2=p 3 | —————————————————————————————————————————————— 4 | |- (typ1,o1)::p1 + (typ2,o2)::p2 = (typ::o)::p 5 | ``` 6 | 7 | ``` 8 | |- notOpt+o = o+notOpt = o |- opt(p1)+opt(p2) = opt(p1+p2) 9 | ``` 10 | 11 | 12 | ``` 13 | |- empty + typ = typ + empty = typ 14 | ``` 15 | 16 | ``` 17 | |- number + number = number 18 | ``` 19 | 20 | ``` 21 | |- string + string = string 22 | ``` 23 | 24 | ``` 25 | |- boolean + boolean = boolean 26 | ``` 27 | 28 | ``` 29 | |- styp1 + styp1’ = styp1’’ … |- stypn + stypn’ = stypn’’ 30 | |- o’’ = o + o’ 31 | ————————————————————————————————————————————————————————— 32 | |- ({x1:styp1, …, xn:stypn}, o) :: p + 33 | ({x1:styp1’, …, xn:stypn’}, o’) :: p’ = 34 | ({x1:styp1’’, …, xn:stypn’’}, o’’) :: p’’ 35 | ``` 36 | 37 | ``` 38 | |- styp1 + styp2 = styp 39 | ———————————————————————————— 40 | |- [styp1::p1] + [styp2::p2] 41 | ``` 42 | -------------------------------------------------------------------------------- /doc/TypeCheckingStyp.md: -------------------------------------------------------------------------------- 1 | ``` 2 | |- 123 : number::1 3 | ``` 4 | 5 | ``` 6 | |- “abc” : string::1 7 | ``` 8 | 9 | ``` 10 | |- true : boolean::1 |- false : boolean::1 11 | ``` 12 | 13 | ``` 14 | ———————————————————- 15 | |- null : (typ?1 :: 1) 16 | ``` 17 | 18 | ``` 19 | |- val1:styp1 … |- valn:stypn 20 | ————————————————————————————————————————————————————— 21 | |- {x1:val1, …, xn:valn} : {x1:styp1, …, xn:stypn}::1 22 | ``` 23 | 24 | ``` 25 | |- [] : [empty] 26 | ``` 27 | 28 | ``` 29 | |- val1:styp1 … |- valn:stypn |- styp1 + … + stypn = styp (n>0) 30 | ——————————————————————————————————————————————————————————————————— 31 | |- [val1, …, valn] : [styp] 32 | ``` 33 | 34 | weakening nullable: 35 | 36 | ``` 37 | |- val : (typ::p) 38 | —————————————————— 39 | |- val : (typ?0 :: p) 40 | ``` 41 | 42 | weakening obj: 43 | 44 | ``` 45 | |- val : ({x1:styp1, …, xn:stypn}, o) :: p 46 | ——————————————————————————————————————————————————————— 47 | |- val : ({x1:styp1, …, xn:stypn, x0:(typ :: 0)}, o) :: p 48 | ``` 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cristiano Calcagno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /opinionated.css: -------------------------------------------------------------------------------- 1 | .node { 2 | -moz-transition: all 0.5s; 3 | -o-transition: all 0.5s; 4 | -ms-transition: all 0.5s; 5 | -webkit-transition: all 0.5s; 6 | transition: all 0.5s; 7 | border-radius: 3px; 8 | } 9 | 10 | .node:hover { 11 | background-color: rgb(220, 245, 243); 12 | cursor: pointer; 13 | } 14 | 15 | .info, .node { 16 | padding: 2px 10px 2px 5px; 17 | font: 14px Helvetica, Arial, sans-serif; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .tree-view_arrow { 25 | -moz-transition: all 0.1s; 26 | -o-transition: all 0.1s; 27 | -ms-transition: all 0.1s; 28 | -webkit-transition: all 0.1s; 29 | transition: all 0.1s; 30 | } 31 | 32 | .tree-view_arrow-empty { 33 | color: yellow; 34 | } 35 | 36 | .column1 { 37 | float: right; 38 | width: 75%; 39 | } 40 | 41 | .column2 { 42 | float: left; 43 | width: 50%; 44 | } 45 | 46 | .column3 { 47 | float: left; 48 | width: 33.33%; 49 | } 50 | 51 | /* Clear floats after the columns */ 52 | .row:after { 53 | content: ""; 54 | display: table; 55 | clear: both; 56 | } 57 | 58 | .centerText { 59 | text-align: center; 60 | width: 66.666%; 61 | } 62 | -------------------------------------------------------------------------------- /src/Index.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as React from "react"; 4 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 5 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 6 | import * as ReactDOMRe from "reason-react/src/ReactDOMRe.js"; 7 | import * as UI$ReactTemplate from "./UI.js"; 8 | import * as Demo$ReactTemplate from "./Demo.js"; 9 | 10 | function Index$Main(Props) { 11 | var diffs = Demo$ReactTemplate.test(/* () */0); 12 | return React.createElement("div", undefined, Belt_Array.mapWithIndex(Belt_List.toArray(diffs), (function (i, diff) { 13 | return React.createElement("div", { 14 | key: String(i) 15 | }, React.createElement("h3", undefined, "Sample " + String(i)), React.createElement(UI$ReactTemplate.Diff.make, { 16 | diff: diff 17 | })); 18 | }))); 19 | } 20 | 21 | var Main = { 22 | make: Index$Main 23 | }; 24 | 25 | ReactDOMRe.renderToElementWithId(React.createElement(Index$Main, { }), "main"); 26 | 27 | export { 28 | Main , 29 | 30 | } 31 | /* Not a pure module */ 32 | -------------------------------------------------------------------------------- /src/Samples.rei: -------------------------------------------------------------------------------- 1 | /** 2 | * Type for samples. 3 | * 4 | * Samples are obtained by adding types in a given order. 5 | * For each addition, the common type and sum type are updated. 6 | * The common type is the type in common to all samples. 7 | * The sum type is the aggregate sum of all samples. 8 | * There is access to the previous version of all values. 9 | */ 10 | type t; 11 | 12 | /** Add one sample. */ 13 | let add: (t, Styp.styp) => t; 14 | 15 | /** 16 | * Add a list as a single sample. 17 | * Equivalent to adding a single sample with the sum type of the elements. 18 | */ 19 | let addMany: (t, list(Styp.styp)) => t; 20 | 21 | /** Empty samples. */ 22 | let empty: t; 23 | 24 | /** Add the samples in the order they appear in the list. */ 25 | let fromList: list(Styp.styp) => t; 26 | 27 | /** Get the list of diffs between the common type and each sample. */ 28 | let getAllDiffs: t => list(Diff.t); 29 | 30 | /** Get the type in common to all the samples. */ 31 | let getCommon: t => Styp.styp; 32 | 33 | /** Get the last sample added. */ 34 | let getLast: t => Styp.styp; 35 | 36 | /** Get the second-last sample added. */ 37 | let getPrev: t => Styp.styp; 38 | 39 | /** Get the common type as it was before adding the last sample. */ 40 | let getPrevCommon: t => Styp.styp; 41 | 42 | /** Get the sum type as it was before adding the last sample. */ 43 | let getPrevSum: t => Styp.styp; 44 | 45 | /** Get the type of the aggregate sum of all the samples. */ 46 | let getSum: t => Styp.styp; 47 | 48 | /** Get the list of samples in the order they were added. */ 49 | let toList: t => list(Styp.styp); -------------------------------------------------------------------------------- /src/Samples.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | samples: list(Styp.styp), /* stored in reverse order */ 3 | sum: Styp.styp, 4 | prevSum: Styp.styp, 5 | common: Styp.styp, 6 | prevCommon: Styp.styp, 7 | }; 8 | 9 | let empty = { 10 | samples: [], 11 | sum: Styp.stypEmpty, 12 | prevSum: Styp.stypEmpty, 13 | common: Styp.stypEmpty, 14 | prevCommon: Styp.stypEmpty, 15 | }; 16 | 17 | let toList = ({samples}) => samples->Belt.List.reverse; 18 | 19 | let getSum = ({sum}) => sum; 20 | 21 | let getCommon = ({common}) => common; 22 | 23 | let getLast = ({samples}) => 24 | switch (samples) { 25 | | [last, ..._] => last 26 | | [] => Styp.stypEmpty 27 | }; 28 | 29 | let getPrev = ({samples}) => 30 | switch (samples) { 31 | | [_, prev, ..._] => prev 32 | | [_] 33 | | [] => Styp.stypEmpty 34 | }; 35 | 36 | let getPrevSum = ({prevSum}) => prevSum; 37 | 38 | let getPrevCommon = ({prevCommon}) => prevCommon; 39 | 40 | let add = (t, styp) => { 41 | let samples = [styp, ...t.samples]; 42 | let prevSum = t.sum; 43 | let sum = TypeCheck.(++)(t.sum, styp); 44 | let prevCommon = t.common; 45 | let common = 46 | t.samples == [] 47 | ? styp : Diff.diff(t.common, styp).stypB->Styp.stripDiffStyp; 48 | {samples, sum, prevSum, common, prevCommon}; 49 | }; 50 | 51 | let addMany = (t, styps) => { 52 | let styp = styps->(Belt.List.reduce(Styp.stypEmpty, TypeCheck.(++))); 53 | t->(add(styp)); 54 | }; 55 | 56 | let fromList = styps => styps->(Belt.List.reduce(empty, add)); 57 | 58 | let getAllDiffs = t => { 59 | let common = t->getCommon; 60 | t->toList->(Belt.List.map(styp => Diff.diff(common, styp))); 61 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statistical-type-inference", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "build": "bsb -make-world", 6 | "start": "bsb -make-world -w -ws _ ", 7 | "clean": "bsb -clean-world", 8 | "serve": "http-server", 9 | "webpack": "webpack", 10 | "parcel": "parcel build index-parcel.html --no-source-maps --out-dir bundledOutputs", 11 | "rollup": "rollup -c rollup.config.browser.js", 12 | "fastpack": "fpack build -o bundledOutputs -n fastpack.js --dev --no-cache", 13 | "test": "rm -rf .cache && time node_modules/fastpack/fpack.exe build -o bundledOutputs -n fastpack.js --dev --no-cache && time yarn webpack && time yarn parcel && time yarn rollup && wc bundledOutputs/*" 14 | }, 15 | "keywords": [ 16 | "BuckleScript" 17 | ], 18 | "author": "", 19 | "license": "MIT", 20 | "dependencies": { 21 | "react": "^16.10.2", 22 | "react-dom": "^16.10.2", 23 | "reason-react": "^0.7.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.0.0-beta.54", 27 | "@babel/plugin-proposal-class-properties": "^7.2.3", 28 | "@babel/preset-env": "^7.0.0-beta.54", 29 | "@babel/preset-flow": "^7.0.0-beta.54", 30 | "@babel/preset-react": "^7.0.0-beta.54", 31 | "babel-loader": "^8.0.0-beta.4", 32 | "bs-platform": "^6.2.0", 33 | "fastpack": "^0.9.1", 34 | "parcel-bundler": "^1.12.3", 35 | "rollup-plugin-commonjs": "^10.1.0", 36 | "rollup-plugin-node-resolve": "^5.2.0", 37 | "rollup-plugin-replace": "^2.2.0", 38 | "rollup-plugin-uglify": "^6.0.3", 39 | "webpack": "^4.41.0", 40 | "webpack-cli": "^3.1.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /doc/Abduction.md: -------------------------------------------------------------------------------- 1 | ``` 2 | |- typ1 + = typ2 |- o1 + = o2 |- p1 + = p2 3 | ————————————————————————————————————————————————————————————— 4 | |- (typ1,o1)::p2 + <(typA,oA)::pA> = (typ2,o2)::p2 5 | ``` 6 | 7 | ``` 8 | |- notOpt + = notOpt 9 | ``` 10 | 11 | ``` 12 | |- notOpt + = opt(p) 13 | ``` 14 | 15 | ``` 16 | |- opt(p) + = opt(p) 17 | ``` 18 | 19 | ``` 20 | p2 > p1 21 | ----------------------------------- 22 | |- opt(p1) + = opt(p2) 23 | ``` 24 | 25 | 26 | ``` 27 | |- empty + = typ 28 | ``` 29 | 30 | ``` 31 | |- number + = number 32 | ``` 33 | 34 | ``` 35 | |- string + = string 36 | ``` 37 | 38 | ``` 39 | |- boolean + = boolean 40 | ``` 41 | 42 | ``` 43 | |- {G1} + <{GA}> = {G2} |- styp1 + = styp2 44 | ——————————————————————————————————————————————————— 45 | |- {G1, x:styp1} + <{GA, x:stypA}> = {G2, x:styp2} 46 | ``` 47 | 48 | ``` 49 | |- {G1} + <{GA}> = {G2} (x not in G1) 50 | ————————————————————————————————————————— 51 | |- {G1} + <{GA, x:styp2}> = {G2, x:styp2} 52 | ``` 53 | 54 | ``` 55 | |- {G1} + <{GA}> = {} |- typ1 + = typ1 |- o1 + = notOpt 56 | ————————————————————————————————————————————————————————————————————— 57 | |- {G1, x:((typ1,o1)::p1)} + <{GA, x:((typA,oA)::(-p1))}> = {} 58 | ``` 59 | 60 | ``` 61 | |- typ1 + <{G, x:(empty, notOpt)::0> = typ2 62 | ——————————————————————————————————————— 63 | |- typ1 + <{G}> = typ2 64 | ``` 65 | 66 | ``` 67 | |- typ1 + <{}> = typ2 68 | ———————————————————— 69 | |- typ1 + = typ2 70 | ``` 71 | 72 | ``` 73 | |- styp1 + = styp2 74 | ———————————————————————————————— 75 | |- [styp1] + <[stypA]> = [styp2] 76 | ``` 77 | 78 | ``` 79 | |- typ1 + <[(empty, notOpt)::0]> = typ2 80 | ——————————————————————————————————— 81 | |- typ1 + = typ2 82 | ``` 83 | -------------------------------------------------------------------------------- /src/Serialize.re: -------------------------------------------------------------------------------- 1 | /* wrap tags as they are not supported by json stringify */ 2 | let tagID = "tag320775503"; 3 | /* wrap undefined as it is not supported by json stringify */ 4 | let undefinedID = "undefined290180182"; 5 | 6 | [@bs.val] external isArray: 'a => bool = "Array.isArray"; 7 | 8 | let copy = (o_: 'a): 'a => { 9 | let o = o_->Obj.magic; 10 | isArray(o) 11 | ? Belt.Array.copy(o)->Obj.magic 12 | : Js.Obj.assign(Js.Obj.empty(), o)->Obj.magic; 13 | }; 14 | let rec wrapTag = o_ => { 15 | let o = Obj.magic(o_); 16 | switch (Js.typeof(o)) { 17 | | "undefined" => [|undefinedID|]->Obj.magic 18 | | "object" when o === Js.null => o 19 | | "object" => 20 | let o1 = copy(o); 21 | let keys = Js.Dict.keys(o1); 22 | for (i in 1 to Belt.Array.length(keys)) { 23 | let key = keys->(Belt.Array.getExn(i - 1)); 24 | o1->(Js.Dict.set(key, o->(Js.Dict.get(key))->Obj.magic->wrapTag)); 25 | }; 26 | let tagOpt = Js.Dict.get(o, "tag"); 27 | switch (tagOpt) { 28 | | None => o1 29 | | Some(tag) => [|tagID, tag, o1|]->Obj.magic 30 | }; 31 | | _ => o 32 | }; 33 | }; 34 | let rec unwrapTag = o_ => { 35 | let o = Obj.magic(o_); 36 | switch (Js.typeof(o)) { 37 | | "object" when o === Js.null => o 38 | | "object" => 39 | switch (isArray(o) ? o->Belt.Array.get(0) : None) { 40 | | Some(id) when id == undefinedID => Js.undefined->Obj.magic 41 | | Some(id) when id == tagID => 42 | let tag = o->(Belt.Array.getExn(1)); 43 | let innerObj = o->(Belt.Array.getExn(2)); 44 | innerObj->(Js.Dict.set("tag", tag)); 45 | unwrapKeys(innerObj); 46 | innerObj; 47 | | _ => 48 | let o1 = copy(o); 49 | unwrapKeys(o1); 50 | o1; 51 | } 52 | | _ => o 53 | }; 54 | } 55 | and unwrapKeys = o => { 56 | let keys = Js.Dict.keys(o); 57 | for (i in 1 to Array.length(keys)) { 58 | let key = keys->(Belt.Array.getExn(i - 1)); 59 | o->(Js.Dict.set(key, o->(Js.Dict.get(key))->Obj.magic->unwrapTag)); 60 | }; 61 | }; 62 | let toString = o => { 63 | let o1 = wrapTag(o); 64 | Js.Json.stringify(o1); 65 | }; 66 | let fromString = s => { 67 | let o = Js.Json.parseExn(s); 68 | unwrapTag(o); 69 | }; -------------------------------------------------------------------------------- /src/DynTypedJson.re: -------------------------------------------------------------------------------- 1 | open Styp; 2 | 3 | type t = { 4 | mutable json: Js.Json.t, 5 | styp: ref(styp), 6 | }; 7 | 8 | let empty = () => {json: Js.Json.null, styp: ref(stypEmpty)}; 9 | 10 | let log = x => { 11 | Js.log2("json:", x.json); 12 | Js.log2("styp:", (x.styp^)->stypToJson->Js.Json.stringify); 13 | }; 14 | 15 | let assignJson = (x, json) => { 16 | x.styp := TypeCheck.(++)(x.styp^, TypeCheck.fromJson(json)); 17 | x.json = json; 18 | }; 19 | 20 | let getFld = (x, fld) => { 21 | if (x.styp^.o != NotOpt) { 22 | Js.log( 23 | "Type error: access field " ++ fld ++ " of object with nullable type", 24 | ); 25 | log(x); 26 | assert(false); 27 | }; 28 | let styp1 = 29 | switch (x.styp^.typ) { 30 | | Styp.Object(dict) => 31 | let styp1 = dict->(Js.Dict.unsafeGet(fld)); 32 | let stypP = x.styp^.p; 33 | let styp1P = styp1.p; 34 | let stypOP = 35 | switch (x.styp^.o) { 36 | | NotOpt => P.zero 37 | | Opt(p) => p 38 | }; 39 | if (stypP != P.(++)(styp1P, stypOP)) { 40 | Js.log( 41 | "Type error: access field " ++ fld ++ " of object with optional type", 42 | ); 43 | log(x); 44 | assert(false); 45 | }; 46 | styp1; 47 | | _ => assert(false) 48 | }; 49 | let json = 50 | switch (x.json->Js.Json.decodeObject) { 51 | | None => assert(false) 52 | | Some(dict) => dict->(Js.Dict.unsafeGet(fld)) 53 | }; 54 | {json, styp: ref(styp1)}; 55 | }; 56 | 57 | let null = empty(); 58 | 59 | let makeNotNullable = x => { 60 | let styp = x.styp^; 61 | if (styp.o != NotOpt) { 62 | let stypOP = 63 | switch (styp.o) { 64 | | NotOpt => P.zero 65 | | Opt(p) => p 66 | }; 67 | x.styp := {...styp, o: NotOpt, p: P.(styp.p -- stypOP)}; 68 | }; 69 | }; 70 | let (==) = (x, y) => { 71 | let magicX = Obj.magic(x); 72 | let magicY = Obj.magic(y); 73 | switch (magicX == null, magicY == null) { 74 | | (true, true) => true 75 | | (true, false) => 76 | makeNotNullable(magicY); 77 | false; 78 | | (false, true) => 79 | makeNotNullable(magicX); 80 | false; 81 | | (false, false) => x == y 82 | }; 83 | }; 84 | 85 | let (!=) = (x, y) => !(x == y); 86 | 87 | let ref = json => { 88 | let x = empty(); 89 | x->(assignJson(json)); 90 | x; 91 | }; 92 | 93 | let (:=) = assignJson; 94 | 95 | let (@) = getFld; -------------------------------------------------------------------------------- /doc/UnionExtension.md: -------------------------------------------------------------------------------- 1 | ``` 2 | styp = (typ:o)::p 3 | typ not of the form union(stypU) 4 | ———————————————————————————————— 5 | |- u(styp) = styp 6 | ``` 7 | 8 | ``` 9 | styp = (typ:o)::p 10 | typ = union(stypU) 11 | —————————————————— 12 | |- u(styp) = stypU 13 | ``` 14 | 15 | ``` 16 | styp1 = (typ1,o1)::p1 styp2 = (typ2,o2)::p2 17 | (typ1 # typ2) 18 | u(styp1) = stypU1 u(styp2) = stypU2 19 | |- stypU1 + stypU2 = stypU |-o1+o2=o p1+p2=p 20 | —————————————————————————————————————————————— 21 | |- styp1 + styp2 = (union(stypU)::o)::p 22 | ``` 23 | 24 | ``` 25 | |- styp1 + styp2 = styp |- stypU1 + stypU2 = stypU 26 | ——————————————————————————————————————————————————————— 27 | |- (styp1 | stypU1) + (styp2 | stypU2) = (styp | stypU) 28 | ``` 29 | 30 | ``` 31 | stypU1 # stypU2 (for all pairs) 32 | ————————————————————————————————————————————————————— 33 | |- stypU1 + stypU2 = stypU1 | stypU2 34 | ``` 35 | 36 | Diff is extended as follows: 37 | 38 | ``` 39 | styp1 = (typ1,o1)::p1 styp2 = (typ2,o2)::p2 40 | typ1 = union(stypU1) 41 | typ2 not of the form union(-) 42 | stypU2 = styp2 43 | styp2' = (union(stypU2),o2)::p2 44 | |- + = styp1,styp2' 45 | ———————————————————————————————————————————— 46 | |- + = styp1,styp2 47 | ``` 48 | 49 | ``` 50 | styp1 = (typ1,o1)::p1 styp2 = (typ2,o2)::p2 51 | typ1 not of the form union(-) 52 | typ2 = union(stypU2) 53 | stypU1 = styp1 54 | styp1' = (union(stypU1),o1)::p1 55 | |- + = styp1',styp2 56 | ———————————————————————————————————————————— 57 | |- + = styp1,styp2 58 | ``` 59 | 60 | ``` 61 | styp1 = (typ1,o1)::p1 styp2 = (typ2,o2)::p2 62 | typ1 = union(stypU1) typ2 = union(stypU2) 63 | |- + = stypU1,stypU2 64 | oA1 = sumO(stypUA1) pA1 = sumP(stypUA1) 65 | oA2 = sumO(stypUA2) pA2 = sumP(stypUA2) 66 | oB = sumO(stypUB) pB = sumP(stypUB) 67 | stypA1 = (union(stypUA1),oA1)::pA1 stypA2 = (union(stypUA2),oA2)::pA2 68 | stypB = (union(stypUB),oB)::pB 69 | —————————————————————————————————————————————————————————————————————— 70 | |- + = styp1,styp2 71 | ``` 72 | 73 | ``` 74 | styp1 = (typ1,o1)::p1 styp2 = (typ2,o2)::p2 75 | |- typ1 + typ2 = typ 76 | |- + = styp1,styp2 77 | |- + = stypU1,stypU2 78 | stypUA1' = stypA1 | stypUA1 stypUA2' = stypA2 | stypUA2 79 | stypUB' = stypB | stypUB 80 | —————————————————————————————————————————————————————————————————————— 81 | |- + = (styp1 | stypU1),(styp2 | stypU2) 82 | ``` 83 | 84 | ``` 85 | stypU1 # stypU2 (for all pairs) 86 | —————————————————————————————————————————————————————————————————————— 87 | |- + = stypU1,stypU2 88 | ``` 89 | -------------------------------------------------------------------------------- /src/Serialize.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; 4 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 5 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; 6 | 7 | var tagID = "tag320775503"; 8 | 9 | var undefinedID = "undefined290180182"; 10 | 11 | function copy(o_) { 12 | var match = Array.isArray(o_); 13 | if (match) { 14 | return o_.slice(0); 15 | } else { 16 | return Object.assign({ }, o_); 17 | } 18 | } 19 | 20 | function wrapTag(o_) { 21 | var match = typeof o_; 22 | switch (match) { 23 | case "object" : 24 | if (o_ === null) { 25 | return o_; 26 | } else { 27 | var o1 = copy(o_); 28 | var keys = Object.keys(o1); 29 | for(var i = 1 ,i_finish = keys.length; i <= i_finish; ++i){ 30 | var key = Belt_Array.getExn(keys, i - 1 | 0); 31 | o1[key] = wrapTag(Js_dict.get(o_, key)); 32 | } 33 | var tagOpt = Js_dict.get(o_, "tag"); 34 | if (tagOpt !== undefined) { 35 | return /* array */[ 36 | tagID, 37 | Caml_option.valFromOption(tagOpt), 38 | o1 39 | ]; 40 | } else { 41 | return o1; 42 | } 43 | } 44 | case "undefined" : 45 | return /* array */[undefinedID]; 46 | default: 47 | return o_; 48 | } 49 | } 50 | 51 | function unwrapTag(o_) { 52 | var match = typeof o_; 53 | if (match === "object" && o_ !== null) { 54 | var match$1 = Array.isArray(o_); 55 | var match$2 = match$1 ? Belt_Array.get(o_, 0) : undefined; 56 | if (match$2 !== undefined) { 57 | var id = Caml_option.valFromOption(match$2); 58 | if (id === undefinedID) { 59 | return ; 60 | } else if (id === tagID) { 61 | var tag = Belt_Array.getExn(o_, 1); 62 | var innerObj = Belt_Array.getExn(o_, 2); 63 | innerObj["tag"] = tag; 64 | unwrapKeys(innerObj); 65 | return innerObj; 66 | } 67 | 68 | } 69 | var o1 = copy(o_); 70 | unwrapKeys(o1); 71 | return o1; 72 | } else { 73 | return o_; 74 | } 75 | } 76 | 77 | function unwrapKeys(o) { 78 | var keys = Object.keys(o); 79 | for(var i = 1 ,i_finish = keys.length; i <= i_finish; ++i){ 80 | var key = Belt_Array.getExn(keys, i - 1 | 0); 81 | o[key] = unwrapTag(Js_dict.get(o, key)); 82 | } 83 | return /* () */0; 84 | } 85 | 86 | function toString(o) { 87 | return JSON.stringify(wrapTag(o)); 88 | } 89 | 90 | function fromString(s) { 91 | return unwrapTag(JSON.parse(s)); 92 | } 93 | 94 | export { 95 | toString , 96 | fromString , 97 | 98 | } 99 | /* No side effect */ 100 | -------------------------------------------------------------------------------- /doc/Diff.md: -------------------------------------------------------------------------------- 1 | Let `stypEmpty = (empty, notOpt, 0)`. 2 | 3 | ``` 4 | |- + = (typ1,typ2) |- + = o1,o2 5 | pB = min(p1,p2) pA1 = p1-pB pA2 = p2-pB 6 | stypA1 = (typA1,oA1)::pA1 stypA2 = (typA2,oA2)::pA2 7 | stypB = (typB,oB)::pB 8 | ———————————————————————————————————————————————————————————————————— 9 | |- + = (typ1,o1)::p1,(typ2,o2)::p2 10 | ``` 11 | 12 | ``` 13 | |- + = notOpt,o2 14 | ``` 15 | 16 | ``` 17 | |- + = o1,notOpt 18 | ``` 19 | 20 | ``` 21 | |- + = opt(p),opt(p) 22 | ``` 23 | 24 | ``` 25 | p1 < p2 26 | ———————————————————————————————————————————————————— 27 | |- + = opt(p1),opt(p2) 28 | ``` 29 | 30 | ``` 31 | p2 < p1 32 | ———————————————————————————————————————————————————— 33 | |- + = opt(p1),opt(p2) 34 | ``` 35 | 36 | ``` 37 | |- + = empty,typ2 38 | ``` 39 | 40 | ``` 41 | |- + = typ1,empty 42 | ``` 43 | 44 | ``` 45 | |- + = number,number 46 | ``` 47 | 48 | ``` 49 | |- + = string,string 50 | ``` 51 | 52 | ``` 53 | |- + = boolean,boolean 54 | ``` 55 | 56 | ``` 57 | |- <{GA1},{GA2}> + <{GB}> = {G1},{G2} 58 | |- + = styp1,styp2 59 | typA1 = stypA1==stypEmpty ? {GA1,x:stypA1} : {GA1} 60 | typA2 = stypA2==stypEmpty ? {GA2,x:stypA2} : {GA2} 61 | typB = {GB,x:stypB} 62 | —————————————————————————————————————————————————————— 63 | |- + = {G1,x:styp1},{G2,x2:styp2} 64 | ``` 65 | 66 | ``` 67 | |- <{GA1},{GA2}> + <{GB}> = {G1},{G2} (x not in G2) 68 | typA1 = styp1==stypEmpty ? {GA1,x:styp1} : {GA1} 69 | ———————————————————————————————————————————————————— 70 | |- + <{GB}> = {G1,x:styp1},{G2} 71 | ``` 72 | 73 | ``` 74 | |- <{GA1},{GA2}> + <{GB}> = {G1},{G2} (x not in G1) 75 | typA2 = styp2==stypEmpty ? {GA2,x:styp2} : {GA2} 76 | ———————————————————————————————————————————————————— 77 | |- <{GA1},tA2> + <{GB}> = {G1},{G2,x:styp2} 78 | ``` 79 | 80 | ``` 81 | |- <{},typA2> + = typ1,typ2 82 | ————————————————————————————————————— 83 | |- + = typ1,typ2 84 | ``` 85 | 86 | ``` 87 | |- + = typ1,typ2 88 | ————————————————————————————————————— 89 | |- + = typ1,typ2 90 | ``` 91 | 92 | ``` 93 | |- + = styp1,styp2 94 | ———————————————————————————————————————————————————— 95 | |- <[stypA1],[stypA2]> + <[stypB]> = [styp1],[styp2] 96 | ``` 97 | 98 | ``` 99 | |- <[stypEmpty], typA2> + = typ1,typ2 100 | ———————————————————————————————————————————— 101 | |- + = typ1,typ2 102 | ``` 103 | 104 | ``` 105 | |- + = typ1,typ2 106 | ———————————————————————————————————————————— 107 | |- + = typ1,typ2 108 | ``` 109 | -------------------------------------------------------------------------------- /src/Samples.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 4 | import * as Diff$ReactTemplate from "./Diff.js"; 5 | import * as Styp$ReactTemplate from "./Styp.js"; 6 | import * as TypeCheck$ReactTemplate from "./TypeCheck.js"; 7 | 8 | var empty = /* record */[ 9 | /* samples : [] */0, 10 | /* sum */Styp$ReactTemplate.stypEmpty, 11 | /* prevSum */Styp$ReactTemplate.stypEmpty, 12 | /* common */Styp$ReactTemplate.stypEmpty, 13 | /* prevCommon */Styp$ReactTemplate.stypEmpty 14 | ]; 15 | 16 | function toList(param) { 17 | return Belt_List.reverse(param[/* samples */0]); 18 | } 19 | 20 | function getSum(param) { 21 | return param[/* sum */1]; 22 | } 23 | 24 | function getCommon(param) { 25 | return param[/* common */3]; 26 | } 27 | 28 | function getLast(param) { 29 | var samples = param[/* samples */0]; 30 | if (samples) { 31 | return samples[0]; 32 | } else { 33 | return Styp$ReactTemplate.stypEmpty; 34 | } 35 | } 36 | 37 | function getPrev(param) { 38 | var samples = param[/* samples */0]; 39 | if (samples) { 40 | var match = samples[1]; 41 | if (match) { 42 | return match[0]; 43 | } else { 44 | return Styp$ReactTemplate.stypEmpty; 45 | } 46 | } else { 47 | return Styp$ReactTemplate.stypEmpty; 48 | } 49 | } 50 | 51 | function getPrevSum(param) { 52 | return param[/* prevSum */2]; 53 | } 54 | 55 | function getPrevCommon(param) { 56 | return param[/* prevCommon */4]; 57 | } 58 | 59 | function add(t, styp) { 60 | var samples_001 = t[/* samples */0]; 61 | var samples = /* :: */[ 62 | styp, 63 | samples_001 64 | ]; 65 | var prevSum = t[/* sum */1]; 66 | var sum = TypeCheck$ReactTemplate.$caret(t[/* sum */1], styp); 67 | var prevCommon = t[/* common */3]; 68 | var match = t[/* samples */0] === /* [] */0; 69 | var common = match ? styp : Styp$ReactTemplate.stripDiffStyp(Diff$ReactTemplate.diff(t[/* common */3], styp)[/* stypB */4]); 70 | return /* record */[ 71 | /* samples */samples, 72 | /* sum */sum, 73 | /* prevSum */prevSum, 74 | /* common */common, 75 | /* prevCommon */prevCommon 76 | ]; 77 | } 78 | 79 | function addMany(t, styps) { 80 | var styp = Belt_List.reduce(styps, Styp$ReactTemplate.stypEmpty, TypeCheck$ReactTemplate.$caret); 81 | return add(t, styp); 82 | } 83 | 84 | function fromList(styps) { 85 | return Belt_List.reduce(styps, empty, add); 86 | } 87 | 88 | function getAllDiffs(t) { 89 | var common = getCommon(t); 90 | return Belt_List.map(toList(t), (function (styp) { 91 | return Diff$ReactTemplate.diff(common, styp); 92 | })); 93 | } 94 | 95 | export { 96 | add , 97 | addMany , 98 | empty , 99 | fromList , 100 | getAllDiffs , 101 | getCommon , 102 | getLast , 103 | getPrev , 104 | getPrevCommon , 105 | getPrevSum , 106 | getSum , 107 | toList , 108 | 109 | } 110 | /* No side effect */ 111 | -------------------------------------------------------------------------------- /src/Demo.re: -------------------------------------------------------------------------------- 1 | let testSmall = () => { 2 | let small = Js.Json.parseExn({| [{"name":null} ] |}); 3 | 4 | let styp = TypeCheck.fromJson(small); 5 | Js.log(styp->Styp.stypToJson->Js.Json.stringify); 6 | }; 7 | 8 | let logDiff = diff => Js.log(diff->Diff.toJson->Js.Json.stringify); 9 | 10 | let testSmallDiff = (~mode=TypeCheck.defaultMode, n) => { 11 | let examples = [| 12 | ({| {"x": "hello"} |}, {| {"x": null, "y":0} |}), 13 | ({| [ "hell", 0, "world"] |}, {| [ "hell", "o", "world"] |}), 14 | ( 15 | {| ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"] |}, 16 | {| ["h", "e", "l", "l", 0, "w", "o", "r", "l", "d"] |}, 17 | ), 18 | ("[null,2,3,4]", "[3]"), 19 | ({| [{"x": {"y" : "hello"}}] |}, {| [{"x": {"z" : "hello"}}] |}), 20 | ("null", "3"), 21 | ({| [{"a":3},{"b":3}] |}, {| [] |}), 22 | ({| [null,{"b":3}] |}, {| [] |}), 23 | ({| [3, "hello", true, 4] |}, {| [1,2,3] |}), 24 | ({| 3 |}, {| "abc" |}), 25 | ( 26 | {|[{"x": ["p","s"]}]|}, 27 | {|[{"x":["p","b"]}, 28 | {"x":["p","s"]}, 29 | {"x": ["p","s"]}]|}, 30 | ), 31 | |]; 32 | let styp1 = 33 | examples 34 | ->(Belt.Array.getExn(n)) 35 | ->fst 36 | ->Js.Json.parseExn 37 | ->(TypeCheck.fromJson(~mode)); 38 | let styp2 = 39 | examples 40 | ->(Belt.Array.getExn(n)) 41 | ->snd 42 | ->Js.Json.parseExn 43 | ->(TypeCheck.fromJson(~mode)); 44 | let diff = Diff.diffCheck(styp1, styp2); 45 | logDiff(diff); 46 | diff; 47 | }; 48 | 49 | let testSamples = (~mode=TypeCheck.defaultMode, ()) => { 50 | let styps = 51 | [{| {"x": 1, "y":"hello"} |}, {| {"x": 2} |}, {| {"x": 3, "y":null} |}] 52 | ->(Belt.List.map(Js.Json.parseExn)) 53 | ->(Belt.List.map(TypeCheck.fromJson(~mode))); 54 | Samples.(styps->fromList->getAllDiffs); 55 | }; 56 | 57 | /* let testBigDiff = () => { 58 | let styp1 = 59 | Query.reasonBronzeThread |. Js.Json.parseExn |. TypeCheck.fromJson; 60 | let styp2 = 61 | Query.reasonPlatinumThread |. Js.Json.parseExn |. TypeCheck.fromJson; 62 | let diff = Diff.diffCheck(styp1, styp2); 63 | logDiff(diff); 64 | diff; 65 | }; */ 66 | 67 | let testDynamicallyTypedJson = () => { 68 | open! DynTypedJson; 69 | 70 | let json1 = Js.Json.parseExn({| null |}); 71 | let json2 = Js.Json.parseExn({| {"b":3} |}); 72 | 73 | let x = ref(json1); 74 | Js.log2("x:", x.json); 75 | 76 | x := json2; 77 | Js.log2("x:", x.json); 78 | 79 | if (x != null) { 80 | let x1 = x @ "b"; 81 | Js.log2("x1:", x1.json); 82 | }; 83 | }; 84 | 85 | let testSerializer = (o, testName) => { 86 | let s = Serialize.toString(o); 87 | let o1 = Serialize.fromString(s); 88 | if (o != o1) { 89 | Js.log("Serialize " ++ testName); 90 | Js.log2("o:", o); 91 | Js.log2("s:", s); 92 | Js.log2("o1:", o1); 93 | assert(false); 94 | }; 95 | o; 96 | }; 97 | 98 | let res = [testSmallDiff(0)]; 99 | /* let res = [testSmallDiff(~mode=TypeCheck.singletonMode, 2)]; */ 100 | /* let res = [testBigDiff()]; */ 101 | /* let res = testSamples(); */ 102 | // let test = () => res->(testSerializer("diff")); 103 | let test = () => res; 104 | 105 | /* fails: serialization of functions 106 | let _ = (x => x + 1) |. testSerializer("function"); 107 | */ 108 | 109 | /* fails: serialization of cyclic values 110 | type cyclic = 111 | | C(cyclic); 112 | let rec cyclic = C(cyclic); 113 | let _ = cyclic |. testSerializer("cyclic"); 114 | */ 115 | 116 | testDynamicallyTypedJson(); -------------------------------------------------------------------------------- /src/TypeCheck.re: -------------------------------------------------------------------------------- 1 | open Styp; 2 | 3 | type mode = {singletonTypes: bool}; 4 | 5 | let defaultMode = {singletonTypes: false}; 6 | let singletonMode = {singletonTypes: true}; 7 | 8 | let rec fromJson = (~mode=defaultMode, json: Js.Json.t): styp => 9 | switch (Js.Json.classify(json)) { 10 | | JSONFalse => { 11 | typ: Boolean(mode.singletonTypes ? Some(false) : None), 12 | o: NotOpt, 13 | p: P.one, 14 | } 15 | | JSONTrue => { 16 | typ: Boolean(mode.singletonTypes ? Some(true) : None), 17 | o: NotOpt, 18 | p: P.one, 19 | } 20 | | JSONNull => {typ: Empty, o: Opt(P.one), p: P.one} 21 | | JSONString(x) => { 22 | typ: String(mode.singletonTypes ? Some(x) : None), 23 | o: NotOpt, 24 | p: P.one, 25 | } 26 | | JSONNumber(x) => { 27 | typ: Number(mode.singletonTypes ? Some(x) : None), 28 | o: NotOpt, 29 | p: P.one, 30 | } 31 | | JSONObject(dict) => 32 | let do_entry = ((lbl, v)) => { 33 | let styp = fromJson(~mode, v); 34 | (lbl, styp); 35 | }; 36 | { 37 | typ: Js.Dict.entries(dict)->(Belt.Array.map(do_entry))->makeObject, 38 | o: NotOpt, 39 | p: P.one, 40 | }; 41 | | JSONArray(a) => 42 | a 43 | ->( 44 | Belt.Array.reduce({typ: Empty, o: NotOpt, p: P.zero}, (styp, json) => 45 | styp ++ fromJson(~mode, json) 46 | ) 47 | ) 48 | ->(styp => {typ: Array(styp), o: NotOpt, p: P.one}) 49 | } 50 | and plusStyp = (styp1, styp2) => { 51 | let typ = 52 | switch (plusTyp(styp1.typ, styp2.typ)) { 53 | | Some(typ) => typ 54 | | None => plusUnion(styp1->stypToUnion, styp2->stypToUnion) 55 | }; 56 | let o = plusO(styp1.o, styp2.o); 57 | open! P; 58 | let p = styp1.p ++ styp2.p; 59 | {typ, o, p}; 60 | } 61 | and plusO = (o1, o2) => 62 | switch (o1, o2) { 63 | | (NotOpt, o) 64 | | (o, NotOpt) => o 65 | | (Opt(p1), Opt(p2)) => 66 | open! P; 67 | Opt(p1 ++ p2); 68 | } 69 | and plusTyp = (typ1, typ2): option(typ) => 70 | switch (typ1, typ2) { 71 | | (Diff(typ, _, _), _) => plusTyp(typ, typ2) 72 | | (_, Diff(typ, _, _)) => plusTyp(typ1, typ) 73 | | (Empty, t) 74 | | (t, Empty) => t->Some 75 | | (Same(t1), t) 76 | | (t, Same(t1)) => plusTyp(t, t1) 77 | | (Number(x), Number(y)) => x == y ? Number(x)->Some : None 78 | | (String(x), String(y)) => x == y ? String(x)->Some : None 79 | | (Boolean(x), Boolean(y)) => x == y ? Boolean(x)->Some : None 80 | | (Object(d1), Object(d2)) => 81 | let d = Js.Dict.empty(); 82 | let doItem = ((lbl, styp)) => 83 | switch (d->(Js.Dict.get(lbl))) { 84 | | None => d->(Js.Dict.set(lbl, styp)) 85 | | Some(styp1) => d->(Js.Dict.set(lbl, styp ++ styp1)) 86 | }; 87 | d1->Js.Dict.entries->(Belt.Array.forEach(doItem)); 88 | d2->Js.Dict.entries->(Belt.Array.forEach(doItem)); 89 | d->Js.Dict.entries->makeObject->Some; 90 | | (Array(styp1), Array(styp2)) => (styp1 ++ styp2)->Array->Some 91 | | (Number(_), _) 92 | | (_, Number(_)) 93 | | (String(_), _) 94 | | (_, String(_)) 95 | | (Boolean(_), _) 96 | | (_, Boolean(_)) 97 | | (Object(_), _) 98 | | (_, Object(_)) 99 | | (Union(_), _) 100 | | (_, Union(_)) => None 101 | } 102 | and plusUnion = (styps1, styps2) => { 103 | let rec findMatch = (t, ts, acc) => 104 | switch (ts) { 105 | | [t1, ...ts1] => 106 | if (plusTyp(t.typ, t1.typ) != None) { 107 | Some((t1, acc->Belt.List.reverse->(Belt.List.concat(ts1)))); 108 | } else { 109 | findMatch(t, ts1, [t1, ...acc]); 110 | } 111 | | [] => None 112 | }; 113 | let rec plus = (ls1, ls2) => 114 | switch (ls1, ls2) { 115 | | ([t1, ...ts1], _) => 116 | switch (findMatch(t1, ls2, [])) { 117 | | None => [t1, ...plus(ts1, ls2)] 118 | | Some((t2, ts2)) => [plusStyp(t1, t2), ...plus(ts1, ts2)] 119 | } 120 | | ([], ts) => ts 121 | }; 122 | plus(styps1, styps2)->makeUnion; 123 | } 124 | and (++) = (styp1, styp2) => plusStyp(styp1, styp2); -------------------------------------------------------------------------------- /src/Styp.re: -------------------------------------------------------------------------------- 1 | module P: { 2 | type t; 3 | let zero: t; 4 | let one: t; 5 | let (++): (t, t) => t; 6 | let (--): (t, t) => t; 7 | let toString: t => string; 8 | let toFloat: t => float; 9 | } = { 10 | type t = int; 11 | let zero = 0; 12 | let one = 1; 13 | 14 | let (++) = (+); 15 | 16 | let (--) = (-); 17 | let toString = string_of_int; 18 | let toFloat = float_of_int; 19 | }; 20 | 21 | type p = P.t; 22 | 23 | type o = 24 | | NotOpt 25 | | Opt(p); 26 | 27 | type typ = 28 | | Empty 29 | | Same(typ) 30 | | Number(option(float)) 31 | | String(option(string)) 32 | | Boolean(option(bool)) 33 | | Object(Js.Dict.t(styp)) 34 | | Array(styp) 35 | | Union(list(styp)) 36 | | Diff(typ, styp, styp) 37 | and styp = { 38 | typ, 39 | o, 40 | p, 41 | }; 42 | 43 | let string_of_bool = b => b ? "true" : "false"; 44 | 45 | let constToString = typ => 46 | switch (typ) { 47 | | Number(x) => 48 | "number" 49 | ++ x->(Belt.Option.mapWithDefault("", f => ":" ++ Js.Float.toString(f))) 50 | | String(x) => "string" ++ x->(Belt.Option.mapWithDefault("", s => ":" ++ s)) 51 | | Boolean(x) => 52 | "boolean" 53 | ++ x->(Belt.Option.mapWithDefault("", b => ":" ++ string_of_bool(b))) 54 | | _ => assert(false) 55 | }; 56 | 57 | let rec stripDiffStyp = styp => {...styp, typ: styp.typ->stripDiffTyp} 58 | and stripDiffTyp = typ => 59 | switch (typ) { 60 | | Empty 61 | | Number(_) 62 | | String(_) 63 | | Boolean(_) => typ 64 | | Same(typ) => Same(typ->stripDiffTyp) 65 | | Object(d) => Js.Dict.map((. styp) => stripDiffStyp(styp), d)->Object 66 | | Array(styp) => Array(styp->stripDiffStyp) 67 | | Union(styps) => styps->(Belt.List.map(stripDiffStyp))->Union 68 | | Diff(t, _, _) => t->stripDiffTyp 69 | }; 70 | 71 | let typIsEmpty = typ => 72 | switch (typ) { 73 | | Empty 74 | | Same(_) => true 75 | | _ => false 76 | }; 77 | 78 | let stypIsNull = (styp: styp) => 79 | switch (styp.o) { 80 | | NotOpt => false 81 | | Opt(p) => styp.typ->typIsEmpty && styp.p == p 82 | }; 83 | 84 | let stypEmpty = {typ: Empty, o: NotOpt, p: P.zero}; 85 | 86 | let stypIsEmpty = styp => 87 | switch (styp) { 88 | | {typ, o: NotOpt, p} when typ->typIsEmpty && p == P.zero => true 89 | | _ => false 90 | }; 91 | 92 | let stypToUnion = styp => 93 | switch (styp.typ) { 94 | | Union(styps) => styps 95 | | _ => [styp] 96 | }; 97 | 98 | let compareEntries = ((lbl1: string, _), (lbl2: string, _)) => 99 | compare(lbl1, lbl2); 100 | 101 | let makeObject = arr => 102 | arr 103 | ->Belt.List.fromArray 104 | ->(Belt.List.sort(compareEntries)) 105 | ->Js.Dict.fromList 106 | ->Object; 107 | 108 | let compareStyp = (x: styp, y: styp): int => compare(x, y); 109 | let makeUnion = styps => styps->(Belt.List.sort(compareStyp))->Union; 110 | 111 | let pToJson = p => p->P.toString->Js.Json.string; 112 | 113 | let rec stypToJson = (styp: styp): Js.Json.t => { 114 | let dict = Js.Dict.empty(); 115 | dict->(Js.Dict.set("typ", styp.typ->typToJson)); 116 | switch (styp.o) { 117 | | NotOpt => () 118 | | Opt(p1) => dict->(Js.Dict.set("opt", p1->pToJson)) 119 | }; 120 | dict->(Js.Dict.set("p", styp.p->pToJson)); 121 | dict->Js.Json.object_; 122 | } 123 | and typToJson = (typ: typ): Js.Json.t => { 124 | open Js.Json; 125 | let arr = a => a->Js.Dict.fromArray->object_; 126 | switch (typ) { 127 | | Empty => [|("kind", "Empty"->string)|]->Js.Dict.fromArray->object_ 128 | | Same(typ) => [|("kind", "Same"->string), ("typ", typ->typToJson)|]->arr 129 | | Number(_) 130 | | String(_) 131 | | Boolean(_) => 132 | let kind = 133 | ( 134 | switch (typ) { 135 | | Number(_) => "Number" 136 | | String(_) => "String" 137 | | Boolean(_) => "Boolean" 138 | | _ => assert(false) 139 | } 140 | ) 141 | ->string; 142 | [|("kind", kind), ("value", typ->constToString->string)|]->arr; 143 | | Object(d) => 144 | let entries = 145 | Js.Dict.entries(d) 146 | ->(Belt.Array.map(((lbl, styp)) => (lbl, styp->stypToJson))) 147 | ->arr; 148 | [|("kind", "Object"->string), ("entries", entries)|]->arr; 149 | | Array(styp) => 150 | let typ = styp->stypToJson; 151 | [|("kind", "Array"->string), ("typ", typ)|]->arr; 152 | | Union(styps) => 153 | let entries = 154 | styps 155 | ->Belt.List.toArray 156 | ->( 157 | Belt.Array.mapWithIndex((i, styp) => 158 | ("u" ++ string_of_int(i), styp->stypToJson) 159 | ) 160 | ) 161 | ->arr; 162 | [|("kind", "Union"->string), ("entries", entries)|]->arr; 163 | | Diff(t, lhs, rhs) => 164 | let common = t->typToJson; 165 | let lhs = lhs->stypToJson; 166 | let rhs = rhs->stypToJson; 167 | [| 168 | ("kind", "Diff"->string), 169 | ("common", common), 170 | ("lhs", lhs), 171 | ("rhs", rhs), 172 | |] 173 | ->arr; 174 | }; 175 | }; -------------------------------------------------------------------------------- /src/DynTypedJson.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Js_json from "bs-platform/lib/es6/js_json.js"; 4 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 5 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; 6 | import * as Styp$ReactTemplate from "./Styp.js"; 7 | import * as Caml_builtin_exceptions from "bs-platform/lib/es6/caml_builtin_exceptions.js"; 8 | import * as TypeCheck$ReactTemplate from "./TypeCheck.js"; 9 | 10 | function empty(param) { 11 | return /* record */[ 12 | /* json */null, 13 | /* styp : record */[/* contents */Styp$ReactTemplate.stypEmpty] 14 | ]; 15 | } 16 | 17 | function log(x) { 18 | console.log("json:", x[/* json */0]); 19 | console.log("styp:", JSON.stringify(Styp$ReactTemplate.stypToJson(x[/* styp */1][0]))); 20 | return /* () */0; 21 | } 22 | 23 | function assignJson(x, json) { 24 | x[/* styp */1][0] = TypeCheck$ReactTemplate.$caret(x[/* styp */1][0], TypeCheck$ReactTemplate.fromJson(undefined, json)); 25 | x[/* json */0] = json; 26 | return /* () */0; 27 | } 28 | 29 | function getFld(x, fld) { 30 | if (x[/* styp */1][0][/* o */1] !== /* NotOpt */0) { 31 | console.log("Type error: access field " + (fld + " of object with nullable type")); 32 | log(x); 33 | throw [ 34 | Caml_builtin_exceptions.assert_failure, 35 | /* tuple */[ 36 | "DynTypedJson.re", 37 | 26, 38 | 4 39 | ] 40 | ]; 41 | } 42 | var match = x[/* styp */1][0][/* typ */0]; 43 | var styp1; 44 | if (typeof match === "number") { 45 | throw [ 46 | Caml_builtin_exceptions.assert_failure, 47 | /* tuple */[ 48 | "DynTypedJson.re", 49 | 47, 50 | 11 51 | ] 52 | ]; 53 | } else if (match.tag === /* Object */4) { 54 | var styp1$1 = match[0][fld]; 55 | var stypP = x[/* styp */1][0][/* p */2]; 56 | var styp1P = styp1$1[/* p */2]; 57 | var match$1 = x[/* styp */1][0][/* o */1]; 58 | var stypOP = match$1 ? match$1[0] : Styp$ReactTemplate.P.zero; 59 | if (Caml_obj.caml_notequal(stypP, Styp$ReactTemplate.P["^"](styp1P, stypOP))) { 60 | console.log("Type error: access field " + (fld + " of object with optional type")); 61 | log(x); 62 | throw [ 63 | Caml_builtin_exceptions.assert_failure, 64 | /* tuple */[ 65 | "DynTypedJson.re", 66 | 44, 67 | 8 68 | ] 69 | ]; 70 | } 71 | styp1 = styp1$1; 72 | } else { 73 | throw [ 74 | Caml_builtin_exceptions.assert_failure, 75 | /* tuple */[ 76 | "DynTypedJson.re", 77 | 47, 78 | 11 79 | ] 80 | ]; 81 | } 82 | var match$2 = Js_json.decodeObject(x[/* json */0]); 83 | var json; 84 | if (match$2 !== undefined) { 85 | json = Caml_option.valFromOption(match$2)[fld]; 86 | } else { 87 | throw [ 88 | Caml_builtin_exceptions.assert_failure, 89 | /* tuple */[ 90 | "DynTypedJson.re", 91 | 51, 92 | 14 93 | ] 94 | ]; 95 | } 96 | return /* record */[ 97 | /* json */json, 98 | /* styp : record */[/* contents */styp1] 99 | ]; 100 | } 101 | 102 | var $$null$1 = /* record */[ 103 | /* json */null, 104 | /* styp : record */[/* contents */Styp$ReactTemplate.stypEmpty] 105 | ]; 106 | 107 | function makeNotNullable(x) { 108 | var styp = x[/* styp */1][0]; 109 | if (styp[/* o */1] !== /* NotOpt */0) { 110 | var match = styp[/* o */1]; 111 | var stypOP = match ? match[0] : Styp$ReactTemplate.P.zero; 112 | x[/* styp */1][0] = /* record */[ 113 | /* typ */styp[/* typ */0], 114 | /* o : NotOpt */0, 115 | /* p */Styp$ReactTemplate.P["--"](styp[/* p */2], stypOP) 116 | ]; 117 | return /* () */0; 118 | } else { 119 | return 0; 120 | } 121 | } 122 | 123 | function $eq(x, y) { 124 | var match = Caml_obj.caml_equal(x, $$null$1); 125 | var match$1 = Caml_obj.caml_equal(y, $$null$1); 126 | if (match) { 127 | if (match$1) { 128 | return true; 129 | } else { 130 | makeNotNullable(y); 131 | return false; 132 | } 133 | } else if (match$1) { 134 | makeNotNullable(x); 135 | return false; 136 | } else { 137 | return Caml_obj.caml_equal(x, y); 138 | } 139 | } 140 | 141 | function $less$great(x, y) { 142 | return !$eq(x, y); 143 | } 144 | 145 | function ref(json) { 146 | var x = /* record */[ 147 | /* json */null, 148 | /* styp : record */[/* contents */Styp$ReactTemplate.stypEmpty] 149 | ]; 150 | assignJson(x, json); 151 | return x; 152 | } 153 | 154 | var $colon$eq = assignJson; 155 | 156 | var $at = getFld; 157 | 158 | export { 159 | empty , 160 | log , 161 | assignJson , 162 | getFld , 163 | $$null$1 as $$null, 164 | makeNotNullable , 165 | $eq , 166 | $less$great , 167 | ref , 168 | $colon$eq , 169 | $at , 170 | 171 | } 172 | /* null Not a pure module */ 173 | -------------------------------------------------------------------------------- /src/Demo.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 4 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 5 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 6 | import * as Diff$ReactTemplate from "./Diff.js"; 7 | import * as Styp$ReactTemplate from "./Styp.js"; 8 | import * as Samples$ReactTemplate from "./Samples.js"; 9 | import * as Caml_builtin_exceptions from "bs-platform/lib/es6/caml_builtin_exceptions.js"; 10 | import * as Serialize$ReactTemplate from "./Serialize.js"; 11 | import * as TypeCheck$ReactTemplate from "./TypeCheck.js"; 12 | import * as DynTypedJson$ReactTemplate from "./DynTypedJson.js"; 13 | 14 | function testSmall(param) { 15 | var small = JSON.parse(" [{\"name\":null} ] "); 16 | var styp = TypeCheck$ReactTemplate.fromJson(undefined, small); 17 | console.log(JSON.stringify(Styp$ReactTemplate.stypToJson(styp))); 18 | return /* () */0; 19 | } 20 | 21 | function logDiff(diff) { 22 | console.log(JSON.stringify(Diff$ReactTemplate.toJson(diff))); 23 | return /* () */0; 24 | } 25 | 26 | function testSmallDiff($staropt$star, n) { 27 | var mode = $staropt$star !== undefined ? $staropt$star : TypeCheck$ReactTemplate.defaultMode; 28 | var examples = /* array */[ 29 | /* tuple */[ 30 | " {\"x\": \"hello\"} ", 31 | " {\"x\": null, \"y\":0} " 32 | ], 33 | /* tuple */[ 34 | " [ \"hell\", 0, \"world\"] ", 35 | " [ \"hell\", \"o\", \"world\"] " 36 | ], 37 | /* tuple */[ 38 | " [\"h\", \"e\", \"l\", \"l\", \"o\", \"w\", \"o\", \"r\", \"l\", \"d\"] ", 39 | " [\"h\", \"e\", \"l\", \"l\", 0, \"w\", \"o\", \"r\", \"l\", \"d\"] " 40 | ], 41 | /* tuple */[ 42 | "[null,2,3,4]", 43 | "[3]" 44 | ], 45 | /* tuple */[ 46 | " [{\"x\": {\"y\" : \"hello\"}}] ", 47 | " [{\"x\": {\"z\" : \"hello\"}}] " 48 | ], 49 | /* tuple */[ 50 | "null", 51 | "3" 52 | ], 53 | /* tuple */[ 54 | " [{\"a\":3},{\"b\":3}] ", 55 | " [] " 56 | ], 57 | /* tuple */[ 58 | " [null,{\"b\":3}] ", 59 | " [] " 60 | ], 61 | /* tuple */[ 62 | " [3, \"hello\", true, 4] ", 63 | " [1,2,3] " 64 | ], 65 | /* tuple */[ 66 | " 3 ", 67 | " \"abc\" " 68 | ], 69 | /* tuple */[ 70 | "[{\"x\": [\"p\",\"s\"]}]", 71 | "[{\"x\":[\"p\",\"b\"]},\n {\"x\":[\"p\",\"s\"]},\n {\"x\": [\"p\",\"s\"]}]" 72 | ] 73 | ]; 74 | var styp1 = TypeCheck$ReactTemplate.fromJson(mode, JSON.parse(Belt_Array.getExn(examples, n)[0])); 75 | var styp2 = TypeCheck$ReactTemplate.fromJson(mode, JSON.parse(Belt_Array.getExn(examples, n)[1])); 76 | var diff = Diff$ReactTemplate.diffCheck(styp1, styp2); 77 | logDiff(diff); 78 | return diff; 79 | } 80 | 81 | function testSamples($staropt$star, param) { 82 | var mode = $staropt$star !== undefined ? $staropt$star : TypeCheck$ReactTemplate.defaultMode; 83 | var partial_arg = mode; 84 | var styps = Belt_List.map(Belt_List.map(/* :: */[ 85 | " {\"x\": 1, \"y\":\"hello\"} ", 86 | /* :: */[ 87 | " {\"x\": 2} ", 88 | /* :: */[ 89 | " {\"x\": 3, \"y\":null} ", 90 | /* [] */0 91 | ] 92 | ] 93 | ], (function (prim) { 94 | return JSON.parse(prim); 95 | })), (function (param) { 96 | return TypeCheck$ReactTemplate.fromJson(partial_arg, param); 97 | })); 98 | return Samples$ReactTemplate.getAllDiffs(Samples$ReactTemplate.fromList(styps)); 99 | } 100 | 101 | function testDynamicallyTypedJson(param) { 102 | var json1 = JSON.parse(" null "); 103 | var json2 = JSON.parse(" {\"b\":3} "); 104 | var x = DynTypedJson$ReactTemplate.ref(json1); 105 | console.log("x:", x[/* json */0]); 106 | DynTypedJson$ReactTemplate.$colon$eq(x, json2); 107 | console.log("x:", x[/* json */0]); 108 | if (DynTypedJson$ReactTemplate.$less$great(x, DynTypedJson$ReactTemplate.$$null)) { 109 | var x1 = DynTypedJson$ReactTemplate.$at(x, "b"); 110 | console.log("x1:", x1[/* json */0]); 111 | return /* () */0; 112 | } else { 113 | return 0; 114 | } 115 | } 116 | 117 | function testSerializer(o, testName) { 118 | var s = Serialize$ReactTemplate.toString(o); 119 | var o1 = Serialize$ReactTemplate.fromString(s); 120 | if (Caml_obj.caml_notequal(o, o1)) { 121 | console.log("Serialize " + testName); 122 | console.log("o:", o); 123 | console.log("s:", s); 124 | console.log("o1:", o1); 125 | throw [ 126 | Caml_builtin_exceptions.assert_failure, 127 | /* tuple */[ 128 | "Demo.re", 129 | 93, 130 | 4 131 | ] 132 | ]; 133 | } 134 | return o; 135 | } 136 | 137 | var res_000 = testSmallDiff(undefined, 0); 138 | 139 | var res = /* :: */[ 140 | res_000, 141 | /* [] */0 142 | ]; 143 | 144 | function test(param) { 145 | return res; 146 | } 147 | 148 | testDynamicallyTypedJson(/* () */0); 149 | 150 | export { 151 | testSmall , 152 | logDiff , 153 | testSmallDiff , 154 | testSamples , 155 | testDynamicallyTypedJson , 156 | testSerializer , 157 | res , 158 | test , 159 | 160 | } 161 | /* res Not a pure module */ 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## REInfer: Runtime Extended Inference 2 | 3 | REInfer performs **R**untime **E**xtended type **Infer**ence on json data. Compared to traditional types, the extended types incorporate some numerical information, such as the number of times a field appears in the data, or the number of times it is null. 4 | 5 | A facility is provided to compare inferred types. This follows the idea of a diff algorithm which takes two values and returns the difference. The difference consists of a common part plus two deltas. Deltas are applied using a sum operation for extended types. The diff algorithm borrows and extends ideas from abduction for shape analysis, applied to type theory instead of program logic. 6 | 7 | A simple UI is provided to experiment with the primitives: it can be used to visualize inferred types and their difference. 8 | 9 | There is a thought experiment exploring of the use of runtime type inference in conjunction with runtime type checking. This combination gives an instrumented semantics that can be used to execute programs. This instrumented semantics has the peculiar property that it can fail at run time in cases where the program is not statically typable. In contrast to ordinary testing, this also applies to programs that **do not fail** when executed under a normal semantics. 10 | 11 | 12 | ## Run Project 13 | 14 | ```sh 15 | npm install 16 | npm start 17 | npm run webpack 18 | # in another tab 19 | npm run serve 20 | ``` 21 | 22 | Then open the served page in the browser and edit `Demo.re`. 23 | 24 | 25 | ### Example of type inference 26 | 27 | ``` 28 | val1: 29 | {"x":"hello"} 30 | 31 | val2: 32 | {"x":null, "y":0} 33 | ``` 34 | 35 | Type inference will produce types `styp1` and `styp2` 36 | 37 | 38 | 39 | The numerical information indicates that fied `x` occurs once. But in the second value it has optional type `? 1`, indicating that 1 (out of 1) value of x is null. 40 | 41 | Numbers begin to add up when using arrays, or when sampling multiple values. 42 | For example, `[null,2,3,4]` has this type: 43 | 44 | 45 | 46 | 47 | ### Example of diff 48 | 49 | Once the types for `val1` and `val2` have been computed, a difference algorithm computes a type `stypB`: 50 | 51 | 52 | 53 | The type highlights what sub-parts which are in common. Also, `lhs` highlights the subpart that the first type has in addition to the common part, and correspondingly for `rhs`. 54 | 55 | It is also possible to look at `stypA1` and `stypA2` that indicate the overall difference between the common type and the two resulting ones: 56 | 57 | 58 | 59 | ### Example of union types 60 | 61 | Some data formats allow differet types of data in the same position. 62 | 63 | For example: `[ "hell", 0, "world"]` has this inferred type: 64 | 65 | 66 | 67 | Diff is also supported with union types. 68 | 69 | ### Example of singleton types 70 | 71 | While by default basic types are at the granularity of string/number/boolean, it's possible to turn on singleton types mode so each constant has a different type. 72 | 73 | For example, `["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]` in singleton type mode has inferred type: 74 | 75 | 76 | 77 | 78 | ## References 79 | 80 | * Sean Grove's talk at ReasonConf 2018 . 81 | 82 | * Tree view UI adapted from [react-treeview](https://github.com/chenglou/react-treeview). 83 | 84 | * Abduction in shape analysis . 85 | 86 | ## License 87 | 88 | This project is [MIT-licensed](./LICENSE.md). 89 | 90 | 91 | ## Formalisation 92 | 93 | 94 | ### Values 95 | 96 | ``` 97 | val ::= 98 | 123 | 99 | “abc” | 100 | true | false | 101 | null | 102 | obj | 103 | [ val1, …, valn ] 104 | 105 | obj ::= { x1:val, …, xn:val } 106 | ``` 107 | 108 | 109 | ### Types 110 | 111 | Types: `t` are ordinary types of a programming language, with `t?` an optional type. 112 | 113 | ``` 114 | t ::= 115 | empty | 116 | number | 117 | string | 118 | boolean | 119 | t? | 120 | {x1:t, …, xn:t} | 121 | [ t ] 122 | ``` 123 | 124 | 125 | ### Statistical Types 126 | 127 | Statistical types are a mutual definition of `styp` and `typ`. 128 | 129 | ``` 130 | o ::= opt(p) | notOpt 131 | ``` 132 | 133 | ``` 134 | styp ::= (typ,o)::p 135 | ``` 136 | 137 | ``` 138 | typ ::= 139 | empty | 140 | number | 141 | string | 142 | boolean | 143 | {x1:styp1, …, xn:stypn} | 144 | [styp] 145 | ``` 146 | 147 | Abbreviation: write `typ?n::p` or `typ::p`. 148 | 149 | ### Type checking for Types 150 | 151 | [Type checking for t](doc/TypeCheckingTypes.md): `|- val : t` 152 | 153 | 154 | ### Erasure of Statistical Types 155 | 156 | [Erasure](doc/Erasure.md): `|- |styp| = t` 157 | 158 | 159 | ### Type checking for Statistical Types 160 | 161 | [Type checking for styp](doc/TypeCheckingStyp.md): `|- val : styp` 162 | 163 | 164 | ### Sum of Statistical Types 165 | 166 | [Sum operations](doc/SumOperations.md): `|- styp1 + styp2 = styp` and `|- typ1 + typ2 = typ` and `|- o1 + o2 = o`. 167 | 168 | Notice this defines a partial commutative monoid. 169 | 170 | 171 | ### Inference of Statistical Types from value samples 172 | 173 | Given a set of sampled data `val1, …, valn` define a process of type inference `|- val1, …, valn -> styp`. The process consists of using the existing inference for arrays: 174 | 175 | ``` 176 | |- [val1, …, valn] : [styp] 177 | ——————————————————————————— 178 | |- val1, …, valn -> styp 179 | ``` 180 | 181 | 182 | ### Abduction for Statistical Types 183 | 184 | [Abduction](doc/Abduction.md): `|- styp1 + = styp2` and `|- typ1 + = typ2` and `|- o1 + = o2`. 185 | 186 | With abduction, the inputs are `styp1` and `styp2`, and the output is `stypA`, the abduced value. 187 | 188 | There are many solutions to the abduction question. We want smallest solution w.r.t. `<=` where `styp1 <= styp2` if there is `styp` such that `|- styp1 + styp = styp2`. 189 | 190 | Abduction aims to compute the smallest representation of the difference between statistical types. 191 | 192 | It turns out that this form of difference is not sufficient because there are incomparable types and the negation corresponding to `+` does not exist. So another form of diff is required. 193 | 194 | 195 | ### Diff for Statistical Types 196 | 197 | [Diff](doc/Diff.md): `|- + = styp1,styp2` 198 | and `|- + = typ1,typ2` and `|- + = o1,o2`. 199 | 200 | The inputs are `styp1` and `styp2` and the outputs are `stypB` and `stypA1` and `stypA2`. The common part is `stypB` and the two deltas are `stypA1` and `stypA2`. 201 | The correctness critera are that `stypA1 + stypB = styp1` and `stypA2 + stypB = styp2`. 202 | 203 | There are many solutions to the diff question. We want the largest solution w.r.t. `<=` for the `B` part, and smallest for the `A1` and `A2` parts, where `styp1 <= styp2` if there is `styp` such that `|- styp1 + styp = styp2`. 204 | 205 | 206 | ### Extension: Union Types 207 | 208 | ``` 209 | stypU ::= styp1 | ... | stypn 210 | 211 | typ += union(stypU) 212 | ``` 213 | 214 | Write `typ1 # typ2` when there is no `typ` such that `|- typ1 + typ2 = typ`, with the corresponding extension `styp1 # styp2`. 215 | 216 | The sum of `stypU` written `|- stypU1 + stypU2 = stypU` is defined below. 217 | The conversion `|- u(styp) = stypU` is also defined below. 218 | 219 | [Union Extension](doc/UnionExtension.md) of `|- styp1 + styp2 = styp` and `|- + = styp1,styp2`. 220 | 221 | 222 | ### Extension: Singleton Types 223 | 224 | ``` 225 | typ ::= 226 | number | 0 | 1 | 2 | ... 227 | string | "hello" | ... 228 | boolean | true | false 229 | ... 230 | ``` 231 | -------------------------------------------------------------------------------- /src/UI.re: -------------------------------------------------------------------------------- 1 | open Styp; 2 | 3 | module Key = { 4 | let counter = ref(0); 5 | let gen = () => { 6 | incr(counter); 7 | string_of_int(counter^); 8 | }; 9 | }; 10 | 11 | module Color = { 12 | let black = "#000000"; 13 | let blue = "#0000FF"; 14 | let brown = "#795E26"; 15 | let green = "#09885A"; 16 | let grey = "#979797"; 17 | let red = "#D60A0A"; 18 | let style = color => ReactDOMRe.Style.make(~color, ()); 19 | }; 20 | 21 | module TreeView = { 22 | type state = {collapsed: bool}; 23 | type actions = 24 | | Click; 25 | 26 | let reducer = (state, action) => { 27 | switch (action) { 28 | | Click => {collapsed: !state.collapsed} 29 | }; 30 | }; 31 | 32 | [@bs.module "react"] 33 | external useReducer: 34 | ([@bs.uncurry] (('state, 'action) => 'state), 'state) => 35 | ('state, (. 'action) => unit) = 36 | "useReducer"; 37 | 38 | [@react.component] 39 | let make = (~nodeLabel, ~collapsed, ~child, _) => { 40 | let (state, dispatch) = useReducer(reducer, {collapsed: collapsed}); 41 | let arrowClassName = 42 | "tree-view_arrow" 43 | ++ (state.collapsed ? " tree-view_arrow-collapsed" : ""); 44 | let containerClassName = 45 | "tree-view_children" 46 | ++ (state.collapsed ? " tree-view_children-collapsed" : ""); 47 | 48 |
49 |
dispatch(. Click)}> 50 |
51 | nodeLabel 52 |
53 |
54 | {state.collapsed ? React.null : child} 55 |
56 |
; 57 | }; 58 | }; 59 | 60 | let node = (~style=?, x) => 61 | {React.string(x)} ; 62 | 63 | let nodeGreen = x => x->(node(~style=Color.(style(green)))); 64 | 65 | let nodeBrown = x => x->(node(~style=Color.(style(brown)))); 66 | 67 | let questionMark = p => 68 | 69 | {React.string(" ? " ++ P.toString(p))} 70 | ; 71 | 72 | type fmt = { 73 | plus: bool /* print '+' in front of number */, 74 | percent: bool /* show percentage instead of absolute numbers */, 75 | hideZeroOne: bool /* hide p when it's 0 or 1 */, 76 | hideP: bool /* hide p completely */, 77 | }; 78 | 79 | let fmtDefault = { 80 | plus: false, 81 | percent: false, 82 | hideZeroOne: false, 83 | hideP: false, 84 | }; 85 | let fmtDelta = {plus: true, percent: false, hideZeroOne: false, hideP: false}; 86 | 87 | let rec toComponentStyp = (styp: styp, ~ctx: p, ~fmt: fmt): React.element => { 88 | let typ = styp.typ->(toComponentT(~ctx=styp.p, ~fmt)); 89 | let style = Color.(style(stypIsNull(styp) ? red : black)); 90 | let shouldAddDecorator = 91 | switch (styp.typ) { 92 | | Empty 93 | | Number(_) 94 | | String(_) 95 | | Boolean(_) => true 96 | | _ => false 97 | }; 98 | stypIsNull(styp) 99 | ?
{React.string("null")}
100 | :
101 | {shouldAddDecorator 102 | ? typ->(addDecorator(~styp, ~right=true, ~ctx, ~fmt)) : typ} 103 |
; 104 | } 105 | and addDecorator = 106 | (x: React.element, ~styp: styp, ~right, ~ctx: p, ~fmt: fmt): React.element => { 107 | let pUnchanged = ctx == P.zero && styp.p == P.zero; 108 | let pString = 109 | if (fmt.percent && ctx != P.zero) { 110 | (P.toFloat(styp.p) /. P.toFloat(ctx))->Js.Float.toString; 111 | } else { 112 | (fmt.plus ? "+" : "") ++ P.toString(styp.p); 113 | }; 114 | let p = 115 | fmt.hideP || fmt.hideZeroOne && (pUnchanged || styp.p == P.one) 116 | ? React.null 117 | : {React.string(pString)} ; 118 | let o = 119 | switch (styp.o) { 120 | | NotOpt => React.null 121 | | Opt(p1) => questionMark(p1) 122 | }; 123 | right ? x p o : p o x ; 124 | } 125 | and toComponentT = (typ: typ, ~ctx: p, ~fmt: fmt): React.element => 126 | switch (typ) { 127 | | Empty => nodeBrown("empty") 128 | | Same(typ) => 129 | (toComponentT(~ctx, ~fmt))} 134 | /> 135 | | Number(_) 136 | | String(_) 137 | | Boolean(_) => typ->constToString->nodeGreen 138 | | Object(d) => 139 | let doEntry = (i, (lbl, styp)) => 140 | 143 | (addDecorator(~styp, ~right=true, ~ctx, ~fmt)) 147 | } 148 | collapsed=false 149 | child={styp->(toComponentStyp(~ctx, ~fmt))} 150 | /> 151 | ; 152 | 153 | React.array(Js.Dict.entries(d)->(Belt.Array.mapWithIndex(doEntry))); 154 | | Array(styp) when stypIsEmpty(styp) => 155 | 156 | {node("[")->(addDecorator(~styp, ~right=false, ~ctx, ~fmt))} 157 | {node("]")} 158 | 159 | | Array(styp) => 160 | 161 | (addDecorator(~styp, ~right=false, ~ctx, ~fmt)) 164 | } 165 | collapsed=false 166 | child={styp->(toComponentStyp(~ctx, ~fmt))} 167 | /> 168 | {node("]")} 169 | 170 | | Union(styps) => 171 | let doEntry = (i, styp) => 172 | (addDecorator(~styp, ~right=true, ~ctx, ~fmt)) 177 | } 178 | collapsed=false 179 | child={styp->(toComponentStyp(~ctx, ~fmt))} 180 | />; 181 | 182 |
183 | "union"->nodeBrown 184 | {styps 185 | ->(Belt.List.mapWithIndex(doEntry)) 186 | ->Belt.List.toArray 187 | ->React.array} 188 |
; 189 | 190 | | Diff(t, lhs, rhs) => 191 | let side = (~left) => { 192 | let lbl = left ? "lhs" : "rhs"; 193 | let styp = left ? lhs : rhs; 194 | nodeBrown} 197 | collapsed=false 198 | child={ 199 | Styp.stypIsEmpty(styp) 200 | ? React.null : styp->(toComponentStyp(~ctx, ~fmt=fmtDelta)) 201 | } 202 | />; 203 | }; 204 |
205 |
{side(~left=true)}
206 |
207 | nodeBrown} 209 | collapsed=false 210 | child={t->(toComponentT(~ctx, ~fmt))} 211 | /> 212 |
213 |
{side(~left=false)}
214 |
; 215 | }; 216 | 217 | module Styp = { 218 | [@react.component] 219 | let make = (~name, ~styp, ~fmt=fmtDefault, _) => { 220 | (toComponentStyp(~ctx=P.zero, ~fmt))} 224 | />; 225 | }; 226 | }; 227 | 228 | module Diff = { 229 | [@react.component] 230 | let make = (~diff: Diff.t, _) => { 231 |
232 |
{React.string @@ "Inferred Types"}
233 |
234 |
235 | 236 |
237 |
238 | 239 |
240 |
241 |
{React.string @@ "Common Part"}
242 |
243 |
{React.string @@ "Deltas"}
244 |
245 |
246 | 247 |
248 |
249 | 250 |
251 |
252 |
; 253 | }; 254 | }; -------------------------------------------------------------------------------- /src/Diff.re: -------------------------------------------------------------------------------- 1 | open Styp; 2 | 3 | type diffStyp = { 4 | styp1: styp, 5 | styp2: styp, 6 | stypA1: styp, 7 | stypA2: styp, 8 | stypB: styp, 9 | }; 10 | 11 | type diffTyp = { 12 | typA1: typ, 13 | typA2: typ, 14 | typB: typ, 15 | }; 16 | 17 | type diffUnion = { 18 | stypUA1: list(styp), 19 | stypUA2: list(styp), 20 | stypUB: list(styp), 21 | }; 22 | 23 | type t = diffStyp; 24 | 25 | /* Inline the differences in the B part */ 26 | let inlineDifferences = true; 27 | 28 | let rec diffStyp = (styp1: styp, styp2: styp): t => 29 | switch (styp1.typ, styp2.typ) { 30 | | (Union(styps1), Union(styps2)) => 31 | diffUnion(styp1, styp2, styps1, styps2) 32 | | (Union(styps1), _) => diffUnion(styp1, styp2, styps1, [styp2]) 33 | | (_, Union(styps2)) => diffUnion(styp1, styp2, [styp1], styps2) 34 | | (typ1, typ2) when TypeCheck.plusTyp(typ1, typ2) == None => 35 | diffUnion(styp1, styp2, [styp1], [styp2]) 36 | | (typ1, typ2) => 37 | let {typA1, typA2, typB} = diffTyp(typ1, typ2); 38 | let (oA1, oA2, oB) = diffO(styp1.o, styp2.o); 39 | let pB = min(styp1.p, styp2.p); 40 | let (pA1, pA2) = (P.(styp1.p -- pB), P.(styp2.p -- pB)); 41 | let stypA1 = {typ: typA1, o: oA1, p: pA1}; 42 | let stypA2 = {typ: typA2, o: oA2, p: pA2}; 43 | let stypB = {typ: typB, o: oB, p: pB}; 44 | open! TypeCheck; 45 | {styp1, styp2, stypA1, stypA2, stypB}; 46 | } 47 | and diffO = (o1: o, o2: o): (o, o, o) => 48 | switch (o1, o2) { 49 | | (NotOpt, _) => (NotOpt, o2, NotOpt) 50 | | (_, NotOpt) => (o1, NotOpt, NotOpt) 51 | | (Opt(p1), Opt(p2)) => ( 52 | p1 > p2 ? Opt(P.(p1 -- p2)) : NotOpt, 53 | p2 > p1 ? Opt(P.(p2 -- p1)) : NotOpt, 54 | Opt(min(p1, p2)), 55 | ) 56 | } 57 | and diffTyp = (typ1: typ, typ2: typ): diffTyp => { 58 | let makeSame = typ => {typA1: Same(typ), typA2: Same(typ), typB: typ}; 59 | switch (typ1, typ2) { 60 | | (Empty | Same(_), _) 61 | | (_, Empty | Same(_)) => {typA1: typ1, typA2: typ2, typB: Empty} 62 | | (Diff(_), _) => {typA1: Empty, typA2: typ2, typB: Empty} 63 | | (_, Diff(_)) => {typA1: typ1, typA2: Empty, typB: Empty} 64 | 65 | | (Number(x), Number(y)) when x == y => makeSame(typ1) 66 | | (String(x), String(y)) when x == y => makeSame(typ1) 67 | | (Boolean(x), Boolean(y)) when x == y => makeSame(typ1) 68 | 69 | | (Object(d1), Object(d2)) => 70 | let dA1 = Js.Dict.empty(); 71 | let dA2 = Js.Dict.empty(); 72 | let dB = Js.Dict.empty(); 73 | let doItem2 = ((lbl, styp2)) => 74 | switch (d1->(Js.Dict.get(lbl))) { 75 | | None => 76 | if (!stypIsEmpty(styp2)) { 77 | dA2->(Js.Dict.set(lbl, styp2)); 78 | } 79 | | Some(styp1) => 80 | let {stypA1, stypA2, stypB} = diffStyp(styp1, styp2); 81 | if (!stypIsEmpty(stypA1)) { 82 | dA1->(Js.Dict.set(lbl, stypA1)); 83 | }; 84 | if (!stypIsEmpty(stypA2)) { 85 | dA2->(Js.Dict.set(lbl, stypA2)); 86 | }; 87 | dB->(Js.Dict.set(lbl, stypB)); 88 | }; 89 | let doItem1 = ((lbl, styp1)) => 90 | switch (d2->(Js.Dict.get(lbl))) { 91 | | None => 92 | if (!stypIsEmpty(styp1)) { 93 | dA1->(Js.Dict.set(lbl, styp1)); 94 | } 95 | | Some(_) => () 96 | }; 97 | d2->Js.Dict.entries->(Belt.Array.forEach(doItem2)); 98 | d1->Js.Dict.entries->(Belt.Array.forEach(doItem1)); 99 | let entries1 = dA1->Js.Dict.entries; 100 | let entries2 = dA2->Js.Dict.entries; 101 | let typA1 = { 102 | let t = entries1->makeObject; 103 | Array.length(entries1) == 0 ? t->Same : t; 104 | }; 105 | let typA2 = { 106 | let t = entries2->makeObject; 107 | Array.length(entries2) == 0 ? t->Same : t; 108 | }; 109 | let typB = dB->Js.Dict.entries->makeObject; 110 | {typA1, typA2, typB}; 111 | 112 | | (Array(styp1), Array(styp2)) => 113 | let {stypA1, stypA2, stypB} = diffStyp(styp1, styp2); 114 | let typA1 = stypIsEmpty(stypA1) ? Same(Array(stypA1)) : Array(stypA1); 115 | let typA2 = stypIsEmpty(stypA2) ? Same(Array(stypA2)) : Array(stypA2); 116 | let typB = Array(stypB); 117 | {typA1, typA2, typB}; 118 | | (Number(_), _) 119 | | (_, Number(_)) 120 | | (String(_), _) 121 | | (_, String(_)) 122 | | (Boolean(_), _) 123 | | (_, Boolean(_)) 124 | | (Object(_), _) 125 | | (_, Object(_)) 126 | | (Union(_), _) 127 | | (_, Union(_)) => assert(false) 128 | }; 129 | } 130 | and diffUnion = (styp1, styp2, styps1: list(styp), styps2: list(styp)): t => { 131 | let rec findMatch = (t, ts, acc) => 132 | switch (ts) { 133 | | [t1, ...ts1] => 134 | if (TypeCheck.plusTyp(t.typ, t1.typ) != None) { 135 | Some((t1, acc->Belt.List.reverse->(Belt.List.concat(ts1)))); 136 | } else { 137 | findMatch(t, ts1, [t1, ...acc]); 138 | } 139 | | [] => None 140 | }; 141 | let rec plus = (ls1, ls2): diffUnion => 142 | switch (ls1, ls2) { 143 | | ([t1, ...ts1], _) => 144 | switch (findMatch(t1, ls2, [])) { 145 | | None => 146 | let diffUnion = plus(ts1, ls2); 147 | {...diffUnion, stypUA1: [t1, ...diffUnion.stypUA1]}; 148 | | Some((t2, ts2)) => 149 | let {stypUA1, stypUA2, stypUB} = plus(ts1, ts2); 150 | let {stypA1, stypA2, stypB} = diffStyp(t1, t2); 151 | { 152 | stypUA1: [stypA1, ...stypUA1], 153 | stypUA2: [stypA2, ...stypUA2], 154 | stypUB: [stypB, ...stypUB], 155 | }; 156 | } 157 | | ([], _) => {stypUA1: [], stypUA2: ls2, stypUB: []} 158 | }; 159 | let {stypUA1, stypUA2, stypUB} = plus(styps1, styps2); 160 | let toUnion = styps => 161 | switch (styps->(Belt.List.keep(styp => !stypIsEmpty(styp)))) { 162 | | [] => Empty 163 | | [styp] => styp.typ 164 | | styps1 => styps1->makeUnion 165 | }; 166 | let toStyp = stypU => { 167 | let typ = stypU->toUnion; 168 | let p = stypU->(Belt.List.reduce(P.zero, (p, styp) => p->(P.(++)(styp.p)))); 169 | let o = 170 | stypU->( 171 | Belt.List.reduce(NotOpt, (o, styp) => o->(TypeCheck.plusO(styp.o))) 172 | ); 173 | {typ, o, p}; 174 | }; 175 | { 176 | styp1, 177 | styp2, 178 | stypA1: stypUA1->toStyp, 179 | stypA2: stypUA2->toStyp, 180 | stypB: stypUB->toStyp, 181 | }; 182 | }; 183 | 184 | let rec combineStyp = (stypA1: styp, stypA2: styp, stypB: styp): styp => 185 | if (stypA1.p != P.zero 186 | || stypA1.o != NotOpt 187 | || stypA2.p != P.zero 188 | || stypA2.o != NotOpt) { 189 | {...stypB, typ: Diff(stypB.typ, stypA1, stypA2)}; 190 | } else { 191 | {...stypB, typ: combineTyp(stypA1.typ, stypA2.typ, stypB.typ)}; 192 | } 193 | and combineTyp = (typA1: typ, typA2: typ, typB: typ): typ => 194 | switch (typA1, typA2, typB) { 195 | | (Array(_) | Empty | Same(_), Array(_) | Empty | Same(_), Array(stypB)) => 196 | let getStyp = typ => 197 | switch (typ) { 198 | | Array(styp) => styp 199 | | _ => stypEmpty 200 | }; 201 | let stypA1 = typA1->getStyp; 202 | let stypA2 = typA2->getStyp; 203 | combineStyp(stypA1, stypA2, stypB)->Array; 204 | | (Object(_) | Empty | Same(_), Object(_) | Empty | Same(_), Object(dictB)) => 205 | let d = Js.Dict.empty(); 206 | let getDict = typ => 207 | switch (typ) { 208 | | Object(dict) => dict 209 | | _ => Js.Dict.empty() 210 | }; 211 | let dictA1 = typA1->getDict; 212 | let dictA2 = typA2->getDict; 213 | let doItem = lbl => { 214 | let getStyp = dict => 215 | switch (dict->(Js.Dict.get(lbl))) { 216 | | None => stypEmpty 217 | | Some(styp) => styp 218 | }; 219 | d->( 220 | Js.Dict.set( 221 | lbl, 222 | combineStyp(dictA1->getStyp, dictA2->getStyp, dictB->getStyp), 223 | ) 224 | ); 225 | }; 226 | Belt.Set.String.( 227 | dictA1 228 | ->Js.Dict.keys 229 | ->fromArray 230 | ->(union(dictA2->Js.Dict.keys->fromArray)) 231 | ->(union(dictB->Js.Dict.keys->fromArray)) 232 | ->(forEach(doItem)) 233 | ); 234 | d->Js.Dict.entries->makeObject; 235 | | _ => typB 236 | }; 237 | 238 | let diff = (styp1, styp2) => { 239 | let d = diffStyp(styp1, styp2); 240 | inlineDifferences 241 | ? {...d, stypB: combineStyp(d.stypA1, d.stypA2, d.stypB)} : d; 242 | }; 243 | let diffCheck = (styp1, styp2) => { 244 | let d = diffStyp(styp1, styp2); 245 | open! TypeCheck; 246 | assert(d.stypB ++ d.stypA1 == styp1); 247 | assert(d.stypB ++ d.stypA2 == styp2); 248 | inlineDifferences 249 | ? {...d, stypB: combineStyp(d.stypA1, d.stypA2, d.stypB)} : d; 250 | }; 251 | 252 | let toJson = (diff: t): Js.Json.t => { 253 | let styp1 = diff.styp1->stypToJson; 254 | let styp2 = diff.styp2->stypToJson; 255 | let stypB = diff.stypB->stypToJson; 256 | let stypA1 = diff.stypA1->stypToJson; 257 | let stypA2 = diff.stypA2->stypToJson; 258 | [| 259 | ("styp1", styp1), 260 | ("styp2", styp2), 261 | ("stypB", stypB), 262 | ("stypA1", stypA1), 263 | ("stypA2", stypA2), 264 | |] 265 | ->Js.Dict.fromArray 266 | ->Js.Json.object_; 267 | }; -------------------------------------------------------------------------------- /src/Styp.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Block from "bs-platform/lib/es6/block.js"; 4 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; 5 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 6 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 7 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 8 | import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; 9 | import * as Caml_primitive from "bs-platform/lib/es6/caml_primitive.js"; 10 | import * as Caml_builtin_exceptions from "bs-platform/lib/es6/caml_builtin_exceptions.js"; 11 | 12 | function $caret(prim, prim$1) { 13 | return prim + prim$1 | 0; 14 | } 15 | 16 | function $neg$neg(prim, prim$1) { 17 | return prim - prim$1 | 0; 18 | } 19 | 20 | function toString(prim) { 21 | return String(prim); 22 | } 23 | 24 | function toFloat(prim) { 25 | return prim; 26 | } 27 | 28 | var P = { 29 | zero: 0, 30 | one: 1, 31 | "^": $caret, 32 | "--": $neg$neg, 33 | toString: toString, 34 | toFloat: toFloat 35 | }; 36 | 37 | function string_of_bool(b) { 38 | if (b) { 39 | return "true"; 40 | } else { 41 | return "false"; 42 | } 43 | } 44 | 45 | function constToString(typ) { 46 | if (typeof typ === "number") { 47 | throw [ 48 | Caml_builtin_exceptions.assert_failure, 49 | /* tuple */[ 50 | "Styp.re", 51 | 54, 52 | 9 53 | ] 54 | ]; 55 | } else { 56 | switch (typ.tag | 0) { 57 | case /* Number */1 : 58 | return "number" + Belt_Option.mapWithDefault(typ[0], "", (function (f) { 59 | return ":" + f.toString(); 60 | })); 61 | case /* String */2 : 62 | return "string" + Belt_Option.mapWithDefault(typ[0], "", (function (s) { 63 | return ":" + s; 64 | })); 65 | case /* Boolean */3 : 66 | return "boolean" + Belt_Option.mapWithDefault(typ[0], "", (function (b) { 67 | return ":" + ( 68 | b ? "true" : "false" 69 | ); 70 | })); 71 | default: 72 | throw [ 73 | Caml_builtin_exceptions.assert_failure, 74 | /* tuple */[ 75 | "Styp.re", 76 | 54, 77 | 9 78 | ] 79 | ]; 80 | } 81 | } 82 | } 83 | 84 | function stripDiffStyp(styp) { 85 | return /* record */[ 86 | /* typ */stripDiffTyp(styp[/* typ */0]), 87 | /* o */styp[/* o */1], 88 | /* p */styp[/* p */2] 89 | ]; 90 | } 91 | 92 | function stripDiffTyp(_typ) { 93 | while(true) { 94 | var typ = _typ; 95 | if (typeof typ === "number") { 96 | return typ; 97 | } else { 98 | switch (typ.tag | 0) { 99 | case /* Same */0 : 100 | return /* Same */Block.__(0, [stripDiffTyp(typ[0])]); 101 | case /* Object */4 : 102 | return /* Object */Block.__(4, [Js_dict.map(stripDiffStyp, typ[0])]); 103 | case /* Array */5 : 104 | return /* Array */Block.__(5, [stripDiffStyp(typ[0])]); 105 | case /* Union */6 : 106 | return /* Union */Block.__(6, [Belt_List.map(typ[0], stripDiffStyp)]); 107 | case /* Diff */7 : 108 | _typ = typ[0]; 109 | continue ; 110 | default: 111 | return typ; 112 | } 113 | } 114 | }; 115 | } 116 | 117 | function typIsEmpty(typ) { 118 | if (typeof typ === "number" || !typ.tag) { 119 | return true; 120 | } else { 121 | return false; 122 | } 123 | } 124 | 125 | function stypIsNull(styp) { 126 | var match = styp[/* o */1]; 127 | if (match && typIsEmpty(styp[/* typ */0])) { 128 | return Caml_obj.caml_equal(styp[/* p */2], match[0]); 129 | } else { 130 | return false; 131 | } 132 | } 133 | 134 | var stypEmpty = /* record */[ 135 | /* typ : Empty */0, 136 | /* o : NotOpt */0, 137 | /* p */0 138 | ]; 139 | 140 | function stypIsEmpty(styp) { 141 | if (styp[/* o */1] || !typIsEmpty(styp[/* typ */0])) { 142 | return false; 143 | } else { 144 | return Caml_obj.caml_equal(styp[/* p */2], 0); 145 | } 146 | } 147 | 148 | function stypToUnion(styp) { 149 | var match = styp[/* typ */0]; 150 | if (typeof match === "number" || match.tag !== /* Union */6) { 151 | return /* :: */[ 152 | styp, 153 | /* [] */0 154 | ]; 155 | } else { 156 | return match[0]; 157 | } 158 | } 159 | 160 | function compareEntries(param, param$1) { 161 | return Caml_primitive.caml_string_compare(param[0], param$1[0]); 162 | } 163 | 164 | function makeObject(arr) { 165 | return /* Object */Block.__(4, [Js_dict.fromList(Belt_List.sort(Belt_List.fromArray(arr), compareEntries))]); 166 | } 167 | 168 | var compareStyp = Caml_obj.caml_compare; 169 | 170 | function makeUnion(styps) { 171 | return /* Union */Block.__(6, [Belt_List.sort(styps, compareStyp)]); 172 | } 173 | 174 | function pToJson(p) { 175 | return String(p); 176 | } 177 | 178 | function stypToJson(styp) { 179 | var dict = { }; 180 | dict["typ"] = typToJson(styp[/* typ */0]); 181 | var match = styp[/* o */1]; 182 | if (match) { 183 | dict["opt"] = String(match[0]); 184 | } 185 | dict["p"] = String(styp[/* p */2]); 186 | return dict; 187 | } 188 | 189 | function typToJson(typ) { 190 | if (typeof typ === "number") { 191 | return Js_dict.fromArray(/* array */[/* tuple */[ 192 | "kind", 193 | "Empty" 194 | ]]); 195 | } else { 196 | switch (typ.tag | 0) { 197 | case /* Same */0 : 198 | return Js_dict.fromArray(/* array */[ 199 | /* tuple */[ 200 | "kind", 201 | "Same" 202 | ], 203 | /* tuple */[ 204 | "typ", 205 | typToJson(typ[0]) 206 | ] 207 | ]); 208 | case /* Object */4 : 209 | var entries = Js_dict.fromArray(Belt_Array.map(Js_dict.entries(typ[0]), (function (param) { 210 | return /* tuple */[ 211 | param[0], 212 | stypToJson(param[1]) 213 | ]; 214 | }))); 215 | return Js_dict.fromArray(/* array */[ 216 | /* tuple */[ 217 | "kind", 218 | "Object" 219 | ], 220 | /* tuple */[ 221 | "entries", 222 | entries 223 | ] 224 | ]); 225 | case /* Array */5 : 226 | var typ$1 = stypToJson(typ[0]); 227 | return Js_dict.fromArray(/* array */[ 228 | /* tuple */[ 229 | "kind", 230 | "Array" 231 | ], 232 | /* tuple */[ 233 | "typ", 234 | typ$1 235 | ] 236 | ]); 237 | case /* Union */6 : 238 | var entries$1 = Js_dict.fromArray(Belt_Array.mapWithIndex(Belt_List.toArray(typ[0]), (function (i, styp) { 239 | return /* tuple */[ 240 | "u" + String(i), 241 | stypToJson(styp) 242 | ]; 243 | }))); 244 | return Js_dict.fromArray(/* array */[ 245 | /* tuple */[ 246 | "kind", 247 | "Union" 248 | ], 249 | /* tuple */[ 250 | "entries", 251 | entries$1 252 | ] 253 | ]); 254 | case /* Diff */7 : 255 | var common = typToJson(typ[0]); 256 | var lhs = stypToJson(typ[1]); 257 | var rhs = stypToJson(typ[2]); 258 | return Js_dict.fromArray(/* array */[ 259 | /* tuple */[ 260 | "kind", 261 | "Diff" 262 | ], 263 | /* tuple */[ 264 | "common", 265 | common 266 | ], 267 | /* tuple */[ 268 | "lhs", 269 | lhs 270 | ], 271 | /* tuple */[ 272 | "rhs", 273 | rhs 274 | ] 275 | ]); 276 | default: 277 | var kind; 278 | if (typeof typ === "number") { 279 | throw [ 280 | Caml_builtin_exceptions.assert_failure, 281 | /* tuple */[ 282 | "Styp.re", 283 | 138, 284 | 15 285 | ] 286 | ]; 287 | } else { 288 | switch (typ.tag | 0) { 289 | case /* Number */1 : 290 | kind = "Number"; 291 | break; 292 | case /* String */2 : 293 | kind = "String"; 294 | break; 295 | case /* Boolean */3 : 296 | kind = "Boolean"; 297 | break; 298 | default: 299 | throw [ 300 | Caml_builtin_exceptions.assert_failure, 301 | /* tuple */[ 302 | "Styp.re", 303 | 138, 304 | 15 305 | ] 306 | ]; 307 | } 308 | } 309 | return Js_dict.fromArray(/* array */[ 310 | /* tuple */[ 311 | "kind", 312 | kind 313 | ], 314 | /* tuple */[ 315 | "value", 316 | constToString(typ) 317 | ] 318 | ]); 319 | } 320 | } 321 | } 322 | 323 | export { 324 | P , 325 | string_of_bool , 326 | constToString , 327 | stripDiffStyp , 328 | stripDiffTyp , 329 | typIsEmpty , 330 | stypIsNull , 331 | stypEmpty , 332 | stypIsEmpty , 333 | stypToUnion , 334 | compareEntries , 335 | makeObject , 336 | compareStyp , 337 | makeUnion , 338 | pToJson , 339 | stypToJson , 340 | typToJson , 341 | 342 | } 343 | /* No side effect */ 344 | -------------------------------------------------------------------------------- /src/TypeCheck.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Block from "bs-platform/lib/es6/block.js"; 4 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; 5 | import * as Js_json from "bs-platform/lib/es6/js_json.js"; 6 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 7 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 8 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 9 | import * as Styp$ReactTemplate from "./Styp.js"; 10 | 11 | var defaultMode = /* record */[/* singletonTypes */false]; 12 | 13 | function fromJson($staropt$star, json) { 14 | var mode = $staropt$star !== undefined ? $staropt$star : defaultMode; 15 | var match = Js_json.classify(json); 16 | if (typeof match === "number") { 17 | switch (match) { 18 | case /* JSONFalse */0 : 19 | var match$1 = mode[/* singletonTypes */0]; 20 | return /* record */[ 21 | /* typ : Boolean */Block.__(3, [match$1 ? false : undefined]), 22 | /* o : NotOpt */0, 23 | /* p */Styp$ReactTemplate.P.one 24 | ]; 25 | case /* JSONTrue */1 : 26 | var match$2 = mode[/* singletonTypes */0]; 27 | return /* record */[ 28 | /* typ : Boolean */Block.__(3, [match$2 ? true : undefined]), 29 | /* o : NotOpt */0, 30 | /* p */Styp$ReactTemplate.P.one 31 | ]; 32 | case /* JSONNull */2 : 33 | return /* record */[ 34 | /* typ : Empty */0, 35 | /* o : Opt */[Styp$ReactTemplate.P.one], 36 | /* p */Styp$ReactTemplate.P.one 37 | ]; 38 | 39 | } 40 | } else { 41 | switch (match.tag | 0) { 42 | case /* JSONString */0 : 43 | var match$3 = mode[/* singletonTypes */0]; 44 | return /* record */[ 45 | /* typ : String */Block.__(2, [match$3 ? match[0] : undefined]), 46 | /* o : NotOpt */0, 47 | /* p */Styp$ReactTemplate.P.one 48 | ]; 49 | case /* JSONNumber */1 : 50 | var match$4 = mode[/* singletonTypes */0]; 51 | return /* record */[ 52 | /* typ : Number */Block.__(1, [match$4 ? match[0] : undefined]), 53 | /* o : NotOpt */0, 54 | /* p */Styp$ReactTemplate.P.one 55 | ]; 56 | case /* JSONObject */2 : 57 | var do_entry = function (param) { 58 | var styp = fromJson(mode, param[1]); 59 | return /* tuple */[ 60 | param[0], 61 | styp 62 | ]; 63 | }; 64 | return /* record */[ 65 | /* typ */Styp$ReactTemplate.makeObject(Belt_Array.map(Js_dict.entries(match[0]), do_entry)), 66 | /* o : NotOpt */0, 67 | /* p */Styp$ReactTemplate.P.one 68 | ]; 69 | case /* JSONArray */3 : 70 | var styp = Belt_Array.reduce(match[0], /* record */[ 71 | /* typ : Empty */0, 72 | /* o : NotOpt */0, 73 | /* p */Styp$ReactTemplate.P.zero 74 | ], (function (styp, json) { 75 | return plusStyp(styp, fromJson(mode, json)); 76 | })); 77 | return /* record */[ 78 | /* typ : Array */Block.__(5, [styp]), 79 | /* o : NotOpt */0, 80 | /* p */Styp$ReactTemplate.P.one 81 | ]; 82 | 83 | } 84 | } 85 | } 86 | 87 | function plusO(o1, o2) { 88 | if (o1) { 89 | if (o2) { 90 | return /* Opt */[Styp$ReactTemplate.P["^"](o1[0], o2[0])]; 91 | } else { 92 | return o1; 93 | } 94 | } else { 95 | return o2; 96 | } 97 | } 98 | 99 | function plusTyp(_typ1, _typ2) { 100 | while(true) { 101 | var typ2 = _typ2; 102 | var typ1 = _typ1; 103 | var exit = 0; 104 | var exit$1 = 0; 105 | if (typeof typ1 === "number") { 106 | exit$1 = 3; 107 | } else { 108 | switch (typ1.tag | 0) { 109 | case /* Number */1 : 110 | var x = typ1[0]; 111 | if (typeof typ2 === "number") { 112 | exit = 2; 113 | } else { 114 | switch (typ2.tag | 0) { 115 | case /* Number */1 : 116 | var match = Caml_obj.caml_equal(x, typ2[0]); 117 | if (match) { 118 | return /* Number */Block.__(1, [x]); 119 | } else { 120 | return ; 121 | } 122 | case /* Array */5 : 123 | return ; 124 | case /* Diff */7 : 125 | exit$1 = 3; 126 | break; 127 | default: 128 | 129 | } 130 | } 131 | break; 132 | case /* String */2 : 133 | var x$1 = typ1[0]; 134 | if (typeof typ2 === "number") { 135 | exit = 2; 136 | } else { 137 | switch (typ2.tag | 0) { 138 | case /* String */2 : 139 | var match$1 = Caml_obj.caml_equal(x$1, typ2[0]); 140 | if (match$1) { 141 | return /* String */Block.__(2, [x$1]); 142 | } else { 143 | return ; 144 | } 145 | case /* Array */5 : 146 | return ; 147 | case /* Diff */7 : 148 | exit$1 = 3; 149 | break; 150 | default: 151 | 152 | } 153 | } 154 | break; 155 | case /* Boolean */3 : 156 | var x$2 = typ1[0]; 157 | if (typeof typ2 === "number") { 158 | exit = 2; 159 | } else { 160 | switch (typ2.tag | 0) { 161 | case /* Boolean */3 : 162 | var match$2 = Caml_obj.caml_equal(x$2, typ2[0]); 163 | if (match$2) { 164 | return /* Boolean */Block.__(3, [x$2]); 165 | } else { 166 | return ; 167 | } 168 | case /* Array */5 : 169 | return ; 170 | case /* Diff */7 : 171 | exit$1 = 3; 172 | break; 173 | default: 174 | 175 | } 176 | } 177 | break; 178 | case /* Object */4 : 179 | if (typeof typ2 === "number") { 180 | exit = 2; 181 | } else { 182 | switch (typ2.tag | 0) { 183 | case /* Object */4 : 184 | var d = { }; 185 | var doItem = (function(d){ 186 | return function doItem(param) { 187 | var styp = param[1]; 188 | var lbl = param[0]; 189 | var match = Js_dict.get(d, lbl); 190 | if (match !== undefined) { 191 | d[lbl] = plusStyp(styp, match); 192 | return /* () */0; 193 | } else { 194 | d[lbl] = styp; 195 | return /* () */0; 196 | } 197 | } 198 | }(d)); 199 | Belt_Array.forEach(Js_dict.entries(typ1[0]), doItem); 200 | Belt_Array.forEach(Js_dict.entries(typ2[0]), doItem); 201 | return Styp$ReactTemplate.makeObject(Js_dict.entries(d)); 202 | case /* Array */5 : 203 | return ; 204 | case /* Diff */7 : 205 | exit$1 = 3; 206 | break; 207 | default: 208 | 209 | } 210 | } 211 | break; 212 | case /* Array */5 : 213 | if (typeof typ2 === "number") { 214 | exit = 2; 215 | } else { 216 | switch (typ2.tag | 0) { 217 | case /* Array */5 : 218 | return /* Array */Block.__(5, [plusStyp(typ1[0], typ2[0])]); 219 | case /* Diff */7 : 220 | exit$1 = 3; 221 | break; 222 | default: 223 | 224 | } 225 | } 226 | break; 227 | case /* Diff */7 : 228 | _typ1 = typ1[0]; 229 | continue ; 230 | default: 231 | exit$1 = 3; 232 | } 233 | } 234 | if (exit$1 === 3) { 235 | if (typeof typ2 === "number") { 236 | if (typeof typ1 === "number") { 237 | return typ2; 238 | } else { 239 | exit = 2; 240 | } 241 | } else if (typ2.tag === /* Diff */7) { 242 | _typ2 = typ2[0]; 243 | continue ; 244 | } else if (typeof typ1 === "number") { 245 | return typ2; 246 | } else { 247 | exit = 2; 248 | } 249 | } 250 | if (exit === 2) { 251 | if (typeof typ2 === "number") { 252 | return typ1; 253 | } else if (typeof typ1 !== "number" && !typ1.tag) { 254 | _typ2 = typ1[0]; 255 | _typ1 = typ2; 256 | continue ; 257 | } 258 | 259 | } 260 | if (typeof typ2 === "number") { 261 | return ; 262 | } else { 263 | switch (typ2.tag | 0) { 264 | case /* Same */0 : 265 | _typ2 = typ2[0]; 266 | continue ; 267 | case /* Array */5 : 268 | return ; 269 | default: 270 | return ; 271 | } 272 | } 273 | }; 274 | } 275 | 276 | function plusUnion(styps1, styps2) { 277 | var findMatch = function (t, _ts, _acc) { 278 | while(true) { 279 | var acc = _acc; 280 | var ts = _ts; 281 | if (ts) { 282 | var ts1 = ts[1]; 283 | var t1 = ts[0]; 284 | if (plusTyp(t[/* typ */0], t1[/* typ */0]) !== undefined) { 285 | return /* tuple */[ 286 | t1, 287 | Belt_List.concat(Belt_List.reverse(acc), ts1) 288 | ]; 289 | } else { 290 | _acc = /* :: */[ 291 | t1, 292 | acc 293 | ]; 294 | _ts = ts1; 295 | continue ; 296 | } 297 | } else { 298 | return ; 299 | } 300 | }; 301 | }; 302 | var plus = function (ls1, ls2) { 303 | if (ls1) { 304 | var ts1 = ls1[1]; 305 | var t1 = ls1[0]; 306 | var match = findMatch(t1, ls2, /* [] */0); 307 | if (match !== undefined) { 308 | var match$1 = match; 309 | return /* :: */[ 310 | plusStyp(t1, match$1[0]), 311 | plus(ts1, match$1[1]) 312 | ]; 313 | } else { 314 | return /* :: */[ 315 | t1, 316 | plus(ts1, ls2) 317 | ]; 318 | } 319 | } else { 320 | return ls2; 321 | } 322 | }; 323 | return Styp$ReactTemplate.makeUnion(plus(styps1, styps2)); 324 | } 325 | 326 | function plusStyp(styp1, styp2) { 327 | var match = plusTyp(styp1[/* typ */0], styp2[/* typ */0]); 328 | var typ = match !== undefined ? match : plusUnion(Styp$ReactTemplate.stypToUnion(styp1), Styp$ReactTemplate.stypToUnion(styp2)); 329 | var o = plusO(styp1[/* o */1], styp2[/* o */1]); 330 | var p = Styp$ReactTemplate.P["^"](styp1[/* p */2], styp2[/* p */2]); 331 | return /* record */[ 332 | /* typ */typ, 333 | /* o */o, 334 | /* p */p 335 | ]; 336 | } 337 | 338 | var $caret = plusStyp; 339 | 340 | var singletonMode = /* record */[/* singletonTypes */true]; 341 | 342 | export { 343 | defaultMode , 344 | singletonMode , 345 | fromJson , 346 | plusStyp , 347 | plusO , 348 | plusTyp , 349 | plusUnion , 350 | $caret , 351 | 352 | } 353 | /* No side effect */ 354 | -------------------------------------------------------------------------------- /src/UI.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as React from "react"; 4 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; 5 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 6 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 7 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 8 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; 9 | import * as Styp$ReactTemplate from "./Styp.js"; 10 | 11 | var counter = /* record */[/* contents */0]; 12 | 13 | function gen(param) { 14 | counter[0] = counter[0] + 1 | 0; 15 | return String(counter[0]); 16 | } 17 | 18 | var Key = { 19 | counter: counter, 20 | gen: gen 21 | }; 22 | 23 | var black = "#000000"; 24 | 25 | var blue = "#0000FF"; 26 | 27 | var brown = "#795E26"; 28 | 29 | var green = "#09885A"; 30 | 31 | var grey = "#979797"; 32 | 33 | var red = "#D60A0A"; 34 | 35 | function style(color) { 36 | return { 37 | color: color 38 | }; 39 | } 40 | 41 | var Color = { 42 | black: black, 43 | blue: blue, 44 | brown: brown, 45 | green: green, 46 | grey: grey, 47 | red: red, 48 | style: style 49 | }; 50 | 51 | function reducer(state, action) { 52 | return /* record */[/* collapsed */!state[/* collapsed */0]]; 53 | } 54 | 55 | function UI$TreeView(Props) { 56 | var nodeLabel = Props.nodeLabel; 57 | var collapsed = Props.collapsed; 58 | var child = Props.child; 59 | var match = React.useReducer(reducer, /* record */[/* collapsed */collapsed]); 60 | var dispatch = match[1]; 61 | var state = match[0]; 62 | var match$1 = state[/* collapsed */0]; 63 | var arrowClassName = "tree-view_arrow" + ( 64 | match$1 ? " tree-view_arrow-collapsed" : "" 65 | ); 66 | var match$2 = state[/* collapsed */0]; 67 | var containerClassName = "tree-view_children" + ( 68 | match$2 ? " tree-view_children-collapsed" : "" 69 | ); 70 | var match$3 = state[/* collapsed */0]; 71 | return React.createElement("div", { 72 | className: "tree-view" 73 | }, React.createElement("div", { 74 | className: "tree-view_item", 75 | onClick: (function (param) { 76 | return dispatch(/* Click */0); 77 | }) 78 | }, React.createElement("div", { 79 | className: arrowClassName 80 | }), React.createElement("span", undefined, nodeLabel)), React.createElement("div", { 81 | className: containerClassName 82 | }, match$3 ? null : child)); 83 | } 84 | 85 | var TreeView = { 86 | reducer: reducer, 87 | make: UI$TreeView 88 | }; 89 | 90 | function node(style, x) { 91 | var tmp = { 92 | className: "node" 93 | }; 94 | if (style !== undefined) { 95 | tmp.style = Caml_option.valFromOption(style); 96 | } 97 | return React.createElement("span", tmp, x); 98 | } 99 | 100 | function nodeGreen(x) { 101 | return node(Caml_option.some(style(green)), x); 102 | } 103 | 104 | function nodeBrown(x) { 105 | return node(Caml_option.some(style(brown)), x); 106 | } 107 | 108 | function questionMark(p) { 109 | return React.createElement("span", { 110 | style: style(red) 111 | }, " ? " + Styp$ReactTemplate.P.toString(p)); 112 | } 113 | 114 | var fmtDefault = /* record */[ 115 | /* plus */false, 116 | /* percent */false, 117 | /* hideZeroOne */false, 118 | /* hideP */false 119 | ]; 120 | 121 | var fmtDelta = /* record */[ 122 | /* plus */true, 123 | /* percent */false, 124 | /* hideZeroOne */false, 125 | /* hideP */false 126 | ]; 127 | 128 | function toComponentStyp(styp, ctx, fmt) { 129 | var typ = toComponentT(styp[/* typ */0], styp[/* p */2], fmt); 130 | var match = Styp$ReactTemplate.stypIsNull(styp); 131 | var style$1 = style(match ? red : black); 132 | var match$1 = styp[/* typ */0]; 133 | var shouldAddDecorator; 134 | if (typeof match$1 === "number") { 135 | shouldAddDecorator = true; 136 | } else { 137 | switch (match$1.tag | 0) { 138 | case /* Number */1 : 139 | case /* String */2 : 140 | case /* Boolean */3 : 141 | shouldAddDecorator = true; 142 | break; 143 | default: 144 | shouldAddDecorator = false; 145 | } 146 | } 147 | var match$2 = Styp$ReactTemplate.stypIsNull(styp); 148 | if (match$2) { 149 | return React.createElement("div", { 150 | className: "node", 151 | style: style$1 152 | }, "null"); 153 | } else { 154 | return React.createElement("div", { 155 | style: style$1 156 | }, shouldAddDecorator ? addDecorator(typ, styp, true, ctx, fmt) : typ); 157 | } 158 | } 159 | 160 | function addDecorator(x, styp, right, ctx, fmt) { 161 | var pUnchanged = Caml_obj.caml_equal(ctx, Styp$ReactTemplate.P.zero) && Caml_obj.caml_equal(styp[/* p */2], Styp$ReactTemplate.P.zero); 162 | var pString; 163 | if (fmt[/* percent */1] && Caml_obj.caml_notequal(ctx, Styp$ReactTemplate.P.zero)) { 164 | pString = (Styp$ReactTemplate.P.toFloat(styp[/* p */2]) / Styp$ReactTemplate.P.toFloat(ctx)).toString(); 165 | } else { 166 | var match = fmt[/* plus */0]; 167 | pString = ( 168 | match ? "+" : "" 169 | ) + Styp$ReactTemplate.P.toString(styp[/* p */2]); 170 | } 171 | var match$1 = fmt[/* hideP */3] || fmt[/* hideZeroOne */2] && (pUnchanged || Caml_obj.caml_equal(styp[/* p */2], Styp$ReactTemplate.P.one)); 172 | var p = match$1 ? null : React.createElement("span", { 173 | style: style(blue) 174 | }, pString); 175 | var match$2 = styp[/* o */1]; 176 | var o = match$2 ? questionMark(match$2[0]) : null; 177 | if (right) { 178 | return React.createElement("span", undefined, x, p, o); 179 | } else { 180 | return React.createElement("span", undefined, p, o, x); 181 | } 182 | } 183 | 184 | function toComponentT(typ, ctx, fmt) { 185 | if (typeof typ === "number") { 186 | return nodeBrown("empty"); 187 | } else { 188 | switch (typ.tag | 0) { 189 | case /* Same */0 : 190 | return React.createElement(UI$TreeView, { 191 | nodeLabel: nodeBrown("same"), 192 | collapsed: true, 193 | child: toComponentT(typ[0], ctx, fmt), 194 | key: "same" 195 | }); 196 | case /* Object */4 : 197 | var doEntry = function (i, param) { 198 | var styp = param[1]; 199 | var match = Caml_obj.caml_equal(styp[/* p */2], Styp$ReactTemplate.P.zero); 200 | return React.createElement("span", { 201 | key: String(i), 202 | style: style(match ? grey : black) 203 | }, React.createElement(UI$TreeView, { 204 | nodeLabel: addDecorator(node(undefined, param[0]), styp, true, ctx, fmt), 205 | collapsed: false, 206 | child: toComponentStyp(styp, ctx, fmt), 207 | key: String(i) 208 | })); 209 | }; 210 | return Belt_Array.mapWithIndex(Js_dict.entries(typ[0]), doEntry); 211 | case /* Array */5 : 212 | var styp = typ[0]; 213 | if (Styp$ReactTemplate.stypIsEmpty(styp)) { 214 | return React.createElement("span", undefined, addDecorator(node(undefined, "["), styp, false, ctx, fmt), node(undefined, "]")); 215 | } else { 216 | var match = Caml_obj.caml_equal(styp[/* p */2], Styp$ReactTemplate.P.zero); 217 | return React.createElement("span", { 218 | style: style(match ? grey : black) 219 | }, React.createElement(UI$TreeView, { 220 | nodeLabel: addDecorator(node(undefined, "["), styp, false, ctx, fmt), 221 | collapsed: false, 222 | child: toComponentStyp(styp, ctx, fmt) 223 | }), node(undefined, "]")); 224 | } 225 | case /* Union */6 : 226 | var doEntry$1 = function (i, styp) { 227 | return React.createElement(UI$TreeView, { 228 | nodeLabel: addDecorator(node(undefined, "u" + String(i + 1 | 0)), styp, true, ctx, fmt), 229 | collapsed: false, 230 | child: toComponentStyp(styp, ctx, fmt), 231 | key: String(i) 232 | }); 233 | }; 234 | return React.createElement("div", undefined, nodeBrown("union"), Belt_List.toArray(Belt_List.mapWithIndex(typ[0], doEntry$1))); 235 | case /* Diff */7 : 236 | var rhs = typ[2]; 237 | var lhs = typ[1]; 238 | var side = function (left) { 239 | var lbl = left ? "lhs" : "rhs"; 240 | var styp = left ? lhs : rhs; 241 | var match = Styp$ReactTemplate.stypIsEmpty(styp); 242 | return React.createElement(UI$TreeView, { 243 | nodeLabel: nodeBrown(lbl), 244 | collapsed: false, 245 | child: match ? null : toComponentStyp(styp, ctx, fmtDelta), 246 | key: lbl 247 | }); 248 | }; 249 | return React.createElement("div", { 250 | className: "row" 251 | }, React.createElement("div", { 252 | className: "column3" 253 | }, side(true)), React.createElement("div", { 254 | className: "column3" 255 | }, React.createElement(UI$TreeView, { 256 | nodeLabel: nodeBrown("common"), 257 | collapsed: false, 258 | child: toComponentT(typ[0], ctx, fmt) 259 | })), React.createElement("div", { 260 | className: "column3" 261 | }, side(false))); 262 | default: 263 | return nodeGreen(Styp$ReactTemplate.constToString(typ)); 264 | } 265 | } 266 | } 267 | 268 | function UI$Styp(Props) { 269 | var name = Props.name; 270 | var styp = Props.styp; 271 | var match = Props.fmt; 272 | var fmt = match !== undefined ? match : fmtDefault; 273 | return React.createElement(UI$TreeView, { 274 | nodeLabel: node(undefined, name), 275 | collapsed: true, 276 | child: toComponentStyp(styp, Styp$ReactTemplate.P.zero, fmt) 277 | }); 278 | } 279 | 280 | var Styp = { 281 | make: UI$Styp 282 | }; 283 | 284 | function UI$Diff(Props) { 285 | var diff = Props.diff; 286 | return React.createElement("div", undefined, React.createElement("div", { 287 | className: "centerText" 288 | }, "Inferred Types"), React.createElement("div", { 289 | className: "row" 290 | }, React.createElement("div", { 291 | className: "column2" 292 | }, React.createElement(UI$Styp, { 293 | name: "styp1", 294 | styp: diff[/* styp1 */0] 295 | })), React.createElement("div", { 296 | className: "column2" 297 | }, React.createElement(UI$Styp, { 298 | name: "styp2", 299 | styp: diff[/* styp2 */1] 300 | }))), React.createElement("div", { 301 | className: "centerText" 302 | }, "Common Part"), React.createElement("div", undefined, React.createElement(UI$Styp, { 303 | name: "stypB", 304 | styp: diff[/* stypB */4] 305 | })), React.createElement("div", { 306 | className: "centerText" 307 | }, "Deltas"), React.createElement("div", { 308 | className: "row" 309 | }, React.createElement("div", { 310 | className: "column2" 311 | }, React.createElement(UI$Styp, { 312 | name: "stypA1", 313 | styp: diff[/* stypA1 */2], 314 | fmt: fmtDelta 315 | })), React.createElement("div", { 316 | className: "column2" 317 | }, React.createElement(UI$Styp, { 318 | name: "stypA2", 319 | styp: diff[/* stypA2 */3], 320 | fmt: fmtDelta 321 | })))); 322 | } 323 | 324 | var Diff = { 325 | make: UI$Diff 326 | }; 327 | 328 | export { 329 | Key , 330 | Color , 331 | TreeView , 332 | node , 333 | nodeGreen , 334 | nodeBrown , 335 | questionMark , 336 | fmtDefault , 337 | fmtDelta , 338 | toComponentStyp , 339 | addDecorator , 340 | toComponentT , 341 | Styp , 342 | Diff , 343 | 344 | } 345 | /* react Not a pure module */ 346 | -------------------------------------------------------------------------------- /src/Diff.js: -------------------------------------------------------------------------------- 1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE 2 | 3 | import * as Block from "bs-platform/lib/es6/block.js"; 4 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; 5 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js"; 6 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js"; 7 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; 8 | import * as Belt_SetString from "bs-platform/lib/es6/belt_SetString.js"; 9 | import * as Styp$ReactTemplate from "./Styp.js"; 10 | import * as Caml_builtin_exceptions from "bs-platform/lib/es6/caml_builtin_exceptions.js"; 11 | import * as TypeCheck$ReactTemplate from "./TypeCheck.js"; 12 | 13 | function diffStyp(styp1, styp2) { 14 | var match = styp1[/* typ */0]; 15 | var match$1 = styp2[/* typ */0]; 16 | var exit = 0; 17 | if (typeof match === "number" || match.tag !== /* Union */6) { 18 | exit = 2; 19 | } else { 20 | var styps1 = match[0]; 21 | var exit$1 = 0; 22 | if (typeof match$1 === "number" || match$1.tag !== /* Union */6) { 23 | exit$1 = 3; 24 | } else { 25 | return diffUnion(styp1, styp2, styps1, match$1[0]); 26 | } 27 | if (exit$1 === 3) { 28 | return diffUnion(styp1, styp2, styps1, /* :: */[ 29 | styp2, 30 | /* [] */0 31 | ]); 32 | } 33 | 34 | } 35 | if (exit === 2 && typeof match$1 !== "number" && match$1.tag === /* Union */6) { 36 | return diffUnion(styp1, styp2, /* :: */[ 37 | styp1, 38 | /* [] */0 39 | ], match$1[0]); 40 | } 41 | if (TypeCheck$ReactTemplate.plusTyp(match, match$1) === undefined) { 42 | return diffUnion(styp1, styp2, /* :: */[ 43 | styp1, 44 | /* [] */0 45 | ], /* :: */[ 46 | styp2, 47 | /* [] */0 48 | ]); 49 | } else { 50 | var match$2 = diffTyp(match, match$1); 51 | var match$3 = diffO(styp1[/* o */1], styp2[/* o */1]); 52 | var pB = Caml_obj.caml_min(styp1[/* p */2], styp2[/* p */2]); 53 | var pA1 = Styp$ReactTemplate.P["--"](styp1[/* p */2], pB); 54 | var pA2 = Styp$ReactTemplate.P["--"](styp2[/* p */2], pB); 55 | var stypA1_000 = /* typ */match$2[/* typA1 */0]; 56 | var stypA1_001 = /* o */match$3[0]; 57 | var stypA1 = /* record */[ 58 | stypA1_000, 59 | stypA1_001, 60 | /* p */pA1 61 | ]; 62 | var stypA2_000 = /* typ */match$2[/* typA2 */1]; 63 | var stypA2_001 = /* o */match$3[1]; 64 | var stypA2 = /* record */[ 65 | stypA2_000, 66 | stypA2_001, 67 | /* p */pA2 68 | ]; 69 | var stypB_000 = /* typ */match$2[/* typB */2]; 70 | var stypB_001 = /* o */match$3[2]; 71 | var stypB = /* record */[ 72 | stypB_000, 73 | stypB_001, 74 | /* p */pB 75 | ]; 76 | return /* record */[ 77 | /* styp1 */styp1, 78 | /* styp2 */styp2, 79 | /* stypA1 */stypA1, 80 | /* stypA2 */stypA2, 81 | /* stypB */stypB 82 | ]; 83 | } 84 | } 85 | 86 | function diffO(o1, o2) { 87 | if (o1) { 88 | if (o2) { 89 | var p2 = o2[0]; 90 | var p1 = o1[0]; 91 | var match = Caml_obj.caml_greaterthan(p1, p2); 92 | var match$1 = Caml_obj.caml_greaterthan(p2, p1); 93 | return /* tuple */[ 94 | match ? /* Opt */[Styp$ReactTemplate.P["--"](p1, p2)] : /* NotOpt */0, 95 | match$1 ? /* Opt */[Styp$ReactTemplate.P["--"](p2, p1)] : /* NotOpt */0, 96 | /* Opt */[Caml_obj.caml_min(p1, p2)] 97 | ]; 98 | } else { 99 | return /* tuple */[ 100 | o1, 101 | /* NotOpt */0, 102 | /* NotOpt */0 103 | ]; 104 | } 105 | } else { 106 | return /* tuple */[ 107 | /* NotOpt */0, 108 | o2, 109 | /* NotOpt */0 110 | ]; 111 | } 112 | } 113 | 114 | function diffTyp(typ1, typ2) { 115 | var makeSame = function (typ) { 116 | return /* record */[ 117 | /* typA1 : Same */Block.__(0, [typ]), 118 | /* typA2 : Same */Block.__(0, [typ]), 119 | /* typB */typ 120 | ]; 121 | }; 122 | var exit = 0; 123 | var exit$1 = 0; 124 | if (typeof typ1 === "number") { 125 | exit$1 = 3; 126 | } else { 127 | switch (typ1.tag | 0) { 128 | case /* Number */1 : 129 | if (typeof typ2 === "number") { 130 | exit$1 = 3; 131 | } else { 132 | switch (typ2.tag | 0) { 133 | case /* Same */0 : 134 | exit$1 = 3; 135 | break; 136 | case /* Number */1 : 137 | if (Caml_obj.caml_equal(typ1[0], typ2[0])) { 138 | return makeSame(typ1); 139 | } else { 140 | exit = 2; 141 | } 142 | break; 143 | case /* Array */5 : 144 | break; 145 | default: 146 | exit = 2; 147 | } 148 | } 149 | break; 150 | case /* String */2 : 151 | if (typeof typ2 === "number") { 152 | exit$1 = 3; 153 | } else { 154 | switch (typ2.tag | 0) { 155 | case /* Same */0 : 156 | exit$1 = 3; 157 | break; 158 | case /* String */2 : 159 | if (Caml_obj.caml_equal(typ1[0], typ2[0])) { 160 | return makeSame(typ1); 161 | } else { 162 | exit = 2; 163 | } 164 | break; 165 | case /* Array */5 : 166 | break; 167 | default: 168 | exit = 2; 169 | } 170 | } 171 | break; 172 | case /* Boolean */3 : 173 | if (typeof typ2 === "number") { 174 | exit$1 = 3; 175 | } else { 176 | switch (typ2.tag | 0) { 177 | case /* Same */0 : 178 | exit$1 = 3; 179 | break; 180 | case /* Boolean */3 : 181 | if (Caml_obj.caml_equal(typ1[0], typ2[0])) { 182 | return makeSame(typ1); 183 | } else { 184 | exit = 2; 185 | } 186 | break; 187 | case /* Array */5 : 188 | break; 189 | default: 190 | exit = 2; 191 | } 192 | } 193 | break; 194 | case /* Object */4 : 195 | var d1 = typ1[0]; 196 | if (typeof typ2 === "number") { 197 | exit$1 = 3; 198 | } else { 199 | switch (typ2.tag | 0) { 200 | case /* Same */0 : 201 | exit$1 = 3; 202 | break; 203 | case /* Object */4 : 204 | var d2 = typ2[0]; 205 | var dA1 = { }; 206 | var dA2 = { }; 207 | var dB = { }; 208 | var doItem2 = function (param) { 209 | var styp2 = param[1]; 210 | var lbl = param[0]; 211 | var match = Js_dict.get(d1, lbl); 212 | if (match !== undefined) { 213 | var match$1 = diffStyp(match, styp2); 214 | var stypA2 = match$1[/* stypA2 */3]; 215 | var stypA1 = match$1[/* stypA1 */2]; 216 | if (!Styp$ReactTemplate.stypIsEmpty(stypA1)) { 217 | dA1[lbl] = stypA1; 218 | } 219 | if (!Styp$ReactTemplate.stypIsEmpty(stypA2)) { 220 | dA2[lbl] = stypA2; 221 | } 222 | dB[lbl] = match$1[/* stypB */4]; 223 | return /* () */0; 224 | } else if (Styp$ReactTemplate.stypIsEmpty(styp2)) { 225 | return 0; 226 | } else { 227 | dA2[lbl] = styp2; 228 | return /* () */0; 229 | } 230 | }; 231 | var doItem1 = function (param) { 232 | var styp1 = param[1]; 233 | var lbl = param[0]; 234 | var match = Js_dict.get(d2, lbl); 235 | if (match !== undefined || Styp$ReactTemplate.stypIsEmpty(styp1)) { 236 | return /* () */0; 237 | } else { 238 | dA1[lbl] = styp1; 239 | return /* () */0; 240 | } 241 | }; 242 | Belt_Array.forEach(Js_dict.entries(d2), doItem2); 243 | Belt_Array.forEach(Js_dict.entries(d1), doItem1); 244 | var entries1 = Js_dict.entries(dA1); 245 | var entries2 = Js_dict.entries(dA2); 246 | var t = Styp$ReactTemplate.makeObject(entries1); 247 | var match = entries1.length === 0; 248 | var typA1 = match ? /* Same */Block.__(0, [t]) : t; 249 | var t$1 = Styp$ReactTemplate.makeObject(entries2); 250 | var match$1 = entries2.length === 0; 251 | var typA2 = match$1 ? /* Same */Block.__(0, [t$1]) : t$1; 252 | var typB = Styp$ReactTemplate.makeObject(Js_dict.entries(dB)); 253 | return /* record */[ 254 | /* typA1 */typA1, 255 | /* typA2 */typA2, 256 | /* typB */typB 257 | ]; 258 | case /* Array */5 : 259 | break; 260 | default: 261 | exit = 2; 262 | } 263 | } 264 | break; 265 | case /* Array */5 : 266 | if (typeof typ2 === "number") { 267 | exit$1 = 3; 268 | } else { 269 | switch (typ2.tag | 0) { 270 | case /* Same */0 : 271 | exit$1 = 3; 272 | break; 273 | case /* Array */5 : 274 | var match$2 = diffStyp(typ1[0], typ2[0]); 275 | var stypA2 = match$2[/* stypA2 */3]; 276 | var stypA1 = match$2[/* stypA1 */2]; 277 | var match$3 = Styp$ReactTemplate.stypIsEmpty(stypA1); 278 | var typA1$1 = match$3 ? /* Same */Block.__(0, [/* Array */Block.__(5, [stypA1])]) : /* Array */Block.__(5, [stypA1]); 279 | var match$4 = Styp$ReactTemplate.stypIsEmpty(stypA2); 280 | var typA2$1 = match$4 ? /* Same */Block.__(0, [/* Array */Block.__(5, [stypA2])]) : /* Array */Block.__(5, [stypA2]); 281 | var typB$1 = /* Array */Block.__(5, [match$2[/* stypB */4]]); 282 | return /* record */[ 283 | /* typA1 */typA1$1, 284 | /* typA2 */typA2$1, 285 | /* typB */typB$1 286 | ]; 287 | default: 288 | exit = 2; 289 | } 290 | } 291 | break; 292 | default: 293 | exit$1 = 3; 294 | } 295 | } 296 | if (exit$1 === 3) { 297 | if (typeof typ2 === "number" || !(typ2.tag && typeof typ1 !== "number")) { 298 | return /* record */[ 299 | /* typA1 */typ1, 300 | /* typA2 */typ2, 301 | /* typB : Empty */0 302 | ]; 303 | } else { 304 | switch (typ1.tag | 0) { 305 | case /* Same */0 : 306 | return /* record */[ 307 | /* typA1 */typ1, 308 | /* typA2 */typ2, 309 | /* typB : Empty */0 310 | ]; 311 | case /* Union */6 : 312 | exit = 2; 313 | break; 314 | case /* Diff */7 : 315 | return /* record */[ 316 | /* typA1 : Empty */0, 317 | /* typA2 */typ2, 318 | /* typB : Empty */0 319 | ]; 320 | 321 | } 322 | } 323 | } 324 | if (exit === 2 && typeof typ2 !== "number") { 325 | switch (typ2.tag | 0) { 326 | case /* Array */5 : 327 | break; 328 | case /* Diff */7 : 329 | return /* record */[ 330 | /* typA1 */typ1, 331 | /* typA2 : Empty */0, 332 | /* typB : Empty */0 333 | ]; 334 | default: 335 | 336 | } 337 | } 338 | throw [ 339 | Caml_builtin_exceptions.assert_failure, 340 | /* tuple */[ 341 | "Diff.re", 342 | 127, 343 | 21 344 | ] 345 | ]; 346 | } 347 | 348 | function diffUnion(styp1, styp2, styps1, styps2) { 349 | var findMatch = function (t, _ts, _acc) { 350 | while(true) { 351 | var acc = _acc; 352 | var ts = _ts; 353 | if (ts) { 354 | var ts1 = ts[1]; 355 | var t1 = ts[0]; 356 | if (TypeCheck$ReactTemplate.plusTyp(t[/* typ */0], t1[/* typ */0]) !== undefined) { 357 | return /* tuple */[ 358 | t1, 359 | Belt_List.concat(Belt_List.reverse(acc), ts1) 360 | ]; 361 | } else { 362 | _acc = /* :: */[ 363 | t1, 364 | acc 365 | ]; 366 | _ts = ts1; 367 | continue ; 368 | } 369 | } else { 370 | return ; 371 | } 372 | }; 373 | }; 374 | var plus = function (ls1, ls2) { 375 | if (ls1) { 376 | var ts1 = ls1[1]; 377 | var t1 = ls1[0]; 378 | var match = findMatch(t1, ls2, /* [] */0); 379 | if (match !== undefined) { 380 | var match$1 = match; 381 | var match$2 = plus(ts1, match$1[1]); 382 | var match$3 = diffStyp(t1, match$1[0]); 383 | return /* record */[ 384 | /* stypUA1 : :: */[ 385 | match$3[/* stypA1 */2], 386 | match$2[/* stypUA1 */0] 387 | ], 388 | /* stypUA2 : :: */[ 389 | match$3[/* stypA2 */3], 390 | match$2[/* stypUA2 */1] 391 | ], 392 | /* stypUB : :: */[ 393 | match$3[/* stypB */4], 394 | match$2[/* stypUB */2] 395 | ] 396 | ]; 397 | } else { 398 | var diffUnion = plus(ts1, ls2); 399 | return /* record */[ 400 | /* stypUA1 : :: */[ 401 | t1, 402 | diffUnion[/* stypUA1 */0] 403 | ], 404 | /* stypUA2 */diffUnion[/* stypUA2 */1], 405 | /* stypUB */diffUnion[/* stypUB */2] 406 | ]; 407 | } 408 | } else { 409 | return /* record */[ 410 | /* stypUA1 : [] */0, 411 | /* stypUA2 */ls2, 412 | /* stypUB : [] */0 413 | ]; 414 | } 415 | }; 416 | var match = plus(styps1, styps2); 417 | var toUnion = function (styps) { 418 | var styps1 = Belt_List.keep(styps, (function (styp) { 419 | return !Styp$ReactTemplate.stypIsEmpty(styp); 420 | })); 421 | if (styps1) { 422 | if (styps1[1]) { 423 | return Styp$ReactTemplate.makeUnion(styps1); 424 | } else { 425 | return styps1[0][/* typ */0]; 426 | } 427 | } else { 428 | return /* Empty */0; 429 | } 430 | }; 431 | var toStyp = function (stypU) { 432 | var typ = toUnion(stypU); 433 | var p = Belt_List.reduce(stypU, Styp$ReactTemplate.P.zero, (function (p, styp) { 434 | return Styp$ReactTemplate.P["^"](p, styp[/* p */2]); 435 | })); 436 | var o = Belt_List.reduce(stypU, /* NotOpt */0, (function (o, styp) { 437 | return TypeCheck$ReactTemplate.plusO(o, styp[/* o */1]); 438 | })); 439 | return /* record */[ 440 | /* typ */typ, 441 | /* o */o, 442 | /* p */p 443 | ]; 444 | }; 445 | return /* record */[ 446 | /* styp1 */styp1, 447 | /* styp2 */styp2, 448 | /* stypA1 */toStyp(match[/* stypUA1 */0]), 449 | /* stypA2 */toStyp(match[/* stypUA2 */1]), 450 | /* stypB */toStyp(match[/* stypUB */2]) 451 | ]; 452 | } 453 | 454 | function combineStyp(stypA1, stypA2, stypB) { 455 | if (Caml_obj.caml_notequal(stypA1[/* p */2], Styp$ReactTemplate.P.zero) || stypA1[/* o */1] !== /* NotOpt */0 || Caml_obj.caml_notequal(stypA2[/* p */2], Styp$ReactTemplate.P.zero) || stypA2[/* o */1] !== /* NotOpt */0) { 456 | return /* record */[ 457 | /* typ : Diff */Block.__(7, [ 458 | stypB[/* typ */0], 459 | stypA1, 460 | stypA2 461 | ]), 462 | /* o */stypB[/* o */1], 463 | /* p */stypB[/* p */2] 464 | ]; 465 | } else { 466 | return /* record */[ 467 | /* typ */combineTyp(stypA1[/* typ */0], stypA2[/* typ */0], stypB[/* typ */0]), 468 | /* o */stypB[/* o */1], 469 | /* p */stypB[/* p */2] 470 | ]; 471 | } 472 | } 473 | 474 | function combineTyp(typA1, typA2, typB) { 475 | var exit = 0; 476 | if (typeof typA1 === "number") { 477 | exit = 2; 478 | } else { 479 | switch (typA1.tag | 0) { 480 | case /* Object */4 : 481 | break; 482 | case /* Same */0 : 483 | case /* Array */5 : 484 | exit = 2; 485 | break; 486 | default: 487 | return typB; 488 | } 489 | } 490 | if (exit === 2) { 491 | var exit$1 = 0; 492 | if (typeof typA2 === "number") { 493 | exit$1 = 3; 494 | } else { 495 | switch (typA2.tag | 0) { 496 | case /* Object */4 : 497 | break; 498 | case /* Same */0 : 499 | case /* Array */5 : 500 | exit$1 = 3; 501 | break; 502 | default: 503 | return typB; 504 | } 505 | } 506 | if (exit$1 === 3) { 507 | if (typeof typB === "number") { 508 | return typB; 509 | } else { 510 | switch (typB.tag | 0) { 511 | case /* Object */4 : 512 | break; 513 | case /* Array */5 : 514 | var getStyp = function (typ) { 515 | if (typeof typ === "number" || typ.tag !== /* Array */5) { 516 | return Styp$ReactTemplate.stypEmpty; 517 | } else { 518 | return typ[0]; 519 | } 520 | }; 521 | var stypA1 = getStyp(typA1); 522 | var stypA2 = getStyp(typA2); 523 | return /* Array */Block.__(5, [combineStyp(stypA1, stypA2, typB[0])]); 524 | default: 525 | return typB; 526 | } 527 | } 528 | } 529 | 530 | } 531 | if (typeof typA1 !== "number" && typA1.tag === /* Array */5) { 532 | return typB; 533 | } 534 | if (typeof typA2 !== "number") { 535 | switch (typA2.tag | 0) { 536 | case /* Same */0 : 537 | case /* Object */4 : 538 | break; 539 | default: 540 | return typB; 541 | } 542 | } 543 | if (typeof typB === "number" || typB.tag !== /* Object */4) { 544 | return typB; 545 | } else { 546 | var dictB = typB[0]; 547 | var d = { }; 548 | var getDict = function (typ) { 549 | if (typeof typ === "number") { 550 | return { }; 551 | } else if (typ.tag === /* Object */4) { 552 | return typ[0]; 553 | } else { 554 | return { }; 555 | } 556 | }; 557 | var dictA1 = getDict(typA1); 558 | var dictA2 = getDict(typA2); 559 | var doItem = function (lbl) { 560 | var getStyp = function (dict) { 561 | var match = Js_dict.get(dict, lbl); 562 | if (match !== undefined) { 563 | return match; 564 | } else { 565 | return Styp$ReactTemplate.stypEmpty; 566 | } 567 | }; 568 | d[lbl] = combineStyp(getStyp(dictA1), getStyp(dictA2), getStyp(dictB)); 569 | return /* () */0; 570 | }; 571 | Belt_SetString.forEach(Belt_SetString.union(Belt_SetString.union(Belt_SetString.fromArray(Object.keys(dictA1)), Belt_SetString.fromArray(Object.keys(dictA2))), Belt_SetString.fromArray(Object.keys(dictB))), doItem); 572 | return Styp$ReactTemplate.makeObject(Js_dict.entries(d)); 573 | } 574 | } 575 | 576 | function diff(styp1, styp2) { 577 | var d = diffStyp(styp1, styp2); 578 | return /* record */[ 579 | /* styp1 */d[/* styp1 */0], 580 | /* styp2 */d[/* styp2 */1], 581 | /* stypA1 */d[/* stypA1 */2], 582 | /* stypA2 */d[/* stypA2 */3], 583 | /* stypB */combineStyp(d[/* stypA1 */2], d[/* stypA2 */3], d[/* stypB */4]) 584 | ]; 585 | } 586 | 587 | function diffCheck(styp1, styp2) { 588 | var d = diffStyp(styp1, styp2); 589 | if (!Caml_obj.caml_equal(TypeCheck$ReactTemplate.$caret(d[/* stypB */4], d[/* stypA1 */2]), styp1)) { 590 | throw [ 591 | Caml_builtin_exceptions.assert_failure, 592 | /* tuple */[ 593 | "Diff.re", 594 | 246, 595 | 2 596 | ] 597 | ]; 598 | } 599 | if (!Caml_obj.caml_equal(TypeCheck$ReactTemplate.$caret(d[/* stypB */4], d[/* stypA2 */3]), styp2)) { 600 | throw [ 601 | Caml_builtin_exceptions.assert_failure, 602 | /* tuple */[ 603 | "Diff.re", 604 | 247, 605 | 2 606 | ] 607 | ]; 608 | } 609 | return /* record */[ 610 | /* styp1 */d[/* styp1 */0], 611 | /* styp2 */d[/* styp2 */1], 612 | /* stypA1 */d[/* stypA1 */2], 613 | /* stypA2 */d[/* stypA2 */3], 614 | /* stypB */combineStyp(d[/* stypA1 */2], d[/* stypA2 */3], d[/* stypB */4]) 615 | ]; 616 | } 617 | 618 | function toJson(diff) { 619 | var styp1 = Styp$ReactTemplate.stypToJson(diff[/* styp1 */0]); 620 | var styp2 = Styp$ReactTemplate.stypToJson(diff[/* styp2 */1]); 621 | var stypB = Styp$ReactTemplate.stypToJson(diff[/* stypB */4]); 622 | var stypA1 = Styp$ReactTemplate.stypToJson(diff[/* stypA1 */2]); 623 | var stypA2 = Styp$ReactTemplate.stypToJson(diff[/* stypA2 */3]); 624 | return Js_dict.fromArray(/* array */[ 625 | /* tuple */[ 626 | "styp1", 627 | styp1 628 | ], 629 | /* tuple */[ 630 | "styp2", 631 | styp2 632 | ], 633 | /* tuple */[ 634 | "stypB", 635 | stypB 636 | ], 637 | /* tuple */[ 638 | "stypA1", 639 | stypA1 640 | ], 641 | /* tuple */[ 642 | "stypA2", 643 | stypA2 644 | ] 645 | ]); 646 | } 647 | 648 | var inlineDifferences = true; 649 | 650 | export { 651 | inlineDifferences , 652 | diffStyp , 653 | diffO , 654 | diffTyp , 655 | diffUnion , 656 | combineStyp , 657 | combineTyp , 658 | diff , 659 | diffCheck , 660 | toJson , 661 | 662 | } 663 | /* No side effect */ 664 | --------------------------------------------------------------------------------