UNPKG

4.32 kBJavaScriptView Raw
1/* @flow weak */
2"use strict";
3
4/**
5 ### DSL for input parameters
6
7 There is a small DSL to help with `forall`. For example the two definitions below are equivalent:
8 ```js
9 var bool_fn_applied_thrice = jsc.forall("bool -> bool", "bool", check);
10 var bool_fn_applied_thrice = jsc.forall(jsc.fn(jsc.bool), jsc.bool, check);
11 ```
12
13 The DSL is based on a subset of language recognized by [typify-parser](https://github.com/phadej/typify-parser):
14 - *identifiers* are fetched from the predefined environment.
15 - *applications* are applied as one could expect: `"array bool"` is evaluated to `jsc.array(jsc.bool)`.
16 - *functions* are supported: `"bool -> bool"` is evaluated to `jsc.fn(jsc.bool)`.
17 - *square brackets* are treated as a shorthand for the array type: `"[nat]"` is evaluated to `jsc.array(jsc.nat)`.
18 - *union*: `"bool | nat"` is evaluated to `jsc.sum([jsc.bool, jsc.nat])`.
19 - **Note** `oneof` cannot be shrinked, because the union is untagged, we don't know which shrink to use.
20 - *conjunction*: `"bool & nat"` is evaluated to `jsc.tuple(jsc.bool, jsc.nat)`.
21 - *anonymous records*: `"{ b: bool; n: nat }"` is evaluated to `jsc.record({ b: jsc.bool, n: jsc.nat })`.
22 - *EXPERIMENTAL: recursive types*: `"rec list -> unit | (nat & list)"`.
23*/
24
25var arbitrary = require("./arbitrary.js");
26var assert = require("assert");
27var record = require("./record.js");
28var array = require("./array.js");
29var fn = require("./fn.js");
30var typifyParser = require("typify-parser");
31var utils = require("./utils.js");
32
33// Forward declarations
34var compileType;
35var compileTypeArray;
36
37function compileIdent(env, type) {
38 var g = env[type.value];
39 if (!g) {
40 throw new Error("Unknown arbitrary: " + type.value);
41 }
42 return g;
43}
44
45function compileApplication(env, type) {
46 var callee = compileType(env, type.callee);
47 var args = compileTypeArray(env, type.args);
48
49 return callee.apply(undefined, args);
50}
51
52function compileFunction(env, type) {
53 // we don't care about argument type
54 var result = compileType(env, type.result);
55 return fn.fn(result);
56}
57
58function compileBrackets(env, type) {
59 var arg = compileType(env, type.arg);
60 return array.array(arg);
61}
62
63function compileDisjunction(env, type) {
64 var args = compileTypeArray(env, type.args);
65 return arbitrary.sum(args);
66}
67
68function compileConjunction(env, type) {
69 var args = compileTypeArray(env, type.args);
70 return arbitrary.tuple(args);
71}
72
73function compileRecord(env, type) {
74 // TODO: use mapValues
75 var spec = {};
76 Object.keys(type.fields).forEach(function (key) {
77 spec[key] = compileType(env, type.fields[key]);
78 });
79 return record.arbitrary(spec);
80}
81
82function compileRecursive(env, type) {
83 assert(type.arg.type === "disjunction", "recursive type's argument should be disjunction");
84
85 // bound variable
86 var name = type.name;
87
88 var par = utils.partition(type.arg.args, function (t) {
89 return typifyParser.freeVars(t).indexOf(name) === -1;
90 });
91
92 var terminal = par[0];
93
94 if (terminal.length === 0) {
95 throw new Error("Recursive type without non-recursive branch");
96 }
97
98 var terminalArb = compileType(env, {
99 type: "disjunction",
100 args: terminal,
101 });
102
103 return arbitrary.recursive(terminalArb, function (arb) {
104 var arbEnv = {};
105 arbEnv[name] = arb;
106 var newEnv = utils.merge(env, arbEnv);
107 return compileType(newEnv, type.arg);
108 });
109}
110
111compileType = function compileTypeFn(env, type) {
112 switch (type.type) {
113 case "ident": return compileIdent(env, type);
114 case "application": return compileApplication(env, type);
115 case "function": return compileFunction(env, type);
116 case "brackets": return compileBrackets(env, type);
117 case "disjunction": return compileDisjunction(env, type);
118 case "conjunction": return compileConjunction(env, type);
119 case "record": return compileRecord(env, type);
120 case "number": return type.value;
121 case "recursive": return compileRecursive(env, type);
122 default: throw new Error("Unsupported typify ast type: " + type.type);
123 }
124};
125
126compileTypeArray = function compileTypeArrayFn(env, types) {
127 return types.map(function (type) {
128 return compileType(env, type);
129 });
130};
131
132function parseTypify(env, str) {
133 var type = typifyParser(str);
134 return compileType(env, type);
135}
136
137module.exports = {
138 parseTypify: parseTypify,
139};