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