1 | 'use strict'
|
2 |
|
3 | const {TokenTypeData} = require('./lang');
|
4 | const {chain, or, and} = require('./frontend');
|
5 |
|
6 | function getIn(obj, path) {
|
7 | const pathGetters = [obj].concat(path.map((_part, index) => path.slice(0, index + 1).reduce((acc, part) => acc.get(part), obj)))
|
8 | return and(...pathGetters);
|
9 | }
|
10 |
|
11 | function has(obj, key) {
|
12 | return obj.get(key).isUndefined().not()
|
13 | }
|
14 |
|
15 | function assignIn(obj, args) {
|
16 | return chain([
|
17 | obj,
|
18 | ...args
|
19 | ]).assign();
|
20 | }
|
21 |
|
22 | function reduce(collection, predicate, initialValue) {
|
23 | return collection.size().eq(0).ternary(
|
24 | initialValue,
|
25 | collection.recursiveMap((loop, value, index) =>
|
26 | predicate(index.eq(0).ternary(
|
27 | initialValue, index.minus(1).recur(loop)), value, index))
|
28 | .get(collection.size().minus(1)))
|
29 | }
|
30 |
|
31 | function concat(a, b) {
|
32 | return chain([a, b]).flatten()
|
33 | }
|
34 |
|
35 | function uniq(arr) {
|
36 | return arr
|
37 | .keyBy(x => x)
|
38 | .keys()
|
39 | }
|
40 |
|
41 | function intersection(a, b) {
|
42 | const array = a.uniq().concat(b.uniq())
|
43 |
|
44 | return array
|
45 | .keyBy((_, idx) => idx)
|
46 | .groupBy(val => val)
|
47 | .filterBy(val => val.keys().size().gt(1))
|
48 | .keys()
|
49 | }
|
50 |
|
51 | function find(collection, predicate, givenCtx) {
|
52 | return collection.values().filter((val, key, ctx) => predicate(val, key, ctx), givenCtx || null).get(0)
|
53 | }
|
54 |
|
55 | function join(arr, separator) {
|
56 | return reduce(arr, (acc, value, index) => index.eq(0).ternary(acc.plus(value), acc.plus(separator).plus(value)), '')
|
57 | }
|
58 |
|
59 | function append(arr, value) {
|
60 | return chain([arr, [value]]).flatten()
|
61 |
|
62 | }
|
63 |
|
64 | function simpleSet(base, key, value) {
|
65 | return chain([base, {[key]: value}]).assign()
|
66 | }
|
67 |
|
68 | function setIn(obj, path, value) {
|
69 | if (!Array.isArray(path) || path.length === 0) {
|
70 | throw new Error('only set with array paths');
|
71 | }
|
72 | path.forEach(val => {
|
73 | if (typeof val !== 'string') {
|
74 | throw new Error('all path parts in set should be strings');
|
75 | }
|
76 | })
|
77 |
|
78 |
|
79 | const currentValues = path.map((part, index) =>
|
80 | or(getIn(obj, path.slice(0, index)), chain({}))
|
81 | )
|
82 |
|
83 | return path.reduceRight((acc, part, index) => simpleSet(currentValues[index], part, acc), value)
|
84 | }
|
85 |
|
86 | function head(array) {
|
87 | return array.get(0)
|
88 | }
|
89 |
|
90 | function reverse(array) {
|
91 | return array.map((item, index) => array.get(array.size().minus(index.plus(1))))
|
92 | }
|
93 |
|
94 | function includesValue(collection, val) {
|
95 | if (typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string') {
|
96 | return collection.anyValues((item, key, ctx) => item.eq(val))
|
97 | }
|
98 | return collection.anyValues((item, key, ctx) => item.eq(ctx), val)
|
99 | }
|
100 |
|
101 | function includes(collection, val) {
|
102 | if (typeof val === 'boolean' || typeof val === 'number' || typeof val === 'string') {
|
103 | return collection.any((item, key, ctx) => item.eq(val))
|
104 | }
|
105 | return collection.any((item, key, ctx) => item.eq(ctx), val)
|
106 | }
|
107 |
|
108 | function findIndex(collection, predicate) {
|
109 | const filtered = collection.map((item, index) => predicate(item, index).ternary(index, chain(-1))).filter(item => item.gt(-1));
|
110 | return filtered.size().ternary(
|
111 | filtered.get(0),
|
112 | -1
|
113 | );
|
114 | }
|
115 |
|
116 | function pick(obj, arr) {
|
117 | const projection = Object.assign({}, ...arr.map(key => ({[key]: obj.get(key)})));
|
118 | return chain(projection).filterBy(item => item.isUndefined().not());
|
119 | }
|
120 |
|
121 | function every(array, predicate) {
|
122 | return array.any((val, key, context) => predicate(val, key, context).not()).not()
|
123 | }
|
124 |
|
125 | function compact(array) {
|
126 | return array.filter(value => value)
|
127 | }
|
128 |
|
129 | function switchCase(obj, caseTuples, defaultCase) {
|
130 | return (caseTuples || []).reduce(
|
131 | (result, caseTuple) => obj.eq(caseTuple[0]).ternary(
|
132 | caseTuple[1],
|
133 | result
|
134 | ),
|
135 | defaultCase || chain(null)
|
136 | )
|
137 | }
|
138 |
|
139 | function conditionalTrace(obj, condition) {
|
140 | return condition.ternary(
|
141 | obj.trace(),
|
142 | obj
|
143 | )
|
144 | }
|
145 |
|
146 | function conditionalBreakpoint(obj, condition) {
|
147 | return condition.ternary(
|
148 | obj.breakpoint(),
|
149 | obj
|
150 | )
|
151 | }
|
152 |
|
153 | function tapTrace(obj, tapFn) {
|
154 | return or(tapFn(obj).trace().ternary(chain(false), chain(false)), obj)
|
155 | }
|
156 |
|
157 | const sugarApi = {
|
158 | getIn,
|
159 | includes,
|
160 | assignIn,
|
161 | reduce,
|
162 | uniq,
|
163 | intersection,
|
164 | concat,
|
165 | find,
|
166 | join,
|
167 | append,
|
168 | setIn,
|
169 | pick,
|
170 | findIndex,
|
171 | includesValue,
|
172 | has,
|
173 | reverse,
|
174 | head,
|
175 | every,
|
176 | simpleSet,
|
177 | compact,
|
178 | switch: switchCase,
|
179 | conditionalTrace,
|
180 | conditionalBreakpoint,
|
181 | tapTrace
|
182 | };
|
183 |
|
184 | Object.keys(sugarApi).forEach(key => {
|
185 | if (TokenTypeData[key]) {
|
186 | throw new Error(`There is a builtin token with this sugar name ${key}`);
|
187 | }
|
188 | });
|
189 |
|
190 | module.exports = sugarApi;
|