theprogrammablemind 7.1.4-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +1473 -0
- package/index.js +27 -0
- package/lines.js +61 -0
- package/package.json +63 -0
- package/readme +132 -0
- package/src/config.js +2129 -0
- package/src/digraph.js +110 -0
- package/src/flatten.js +143 -0
- package/src/generators.js +282 -0
- package/src/helpers.js +216 -0
- package/src/semantics.js +293 -0
- package/src/unflatten.js +128 -0
package/src/helpers.js
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
const deepEqual = require('deep-equal')
|
2
|
+
const stringify = require('json-stable-stringify')
|
3
|
+
|
4
|
+
// properties - the properties that correspond to types
|
5
|
+
// types - the expected types of the properties
|
6
|
+
// returns list of properties found matching order of types
|
7
|
+
|
8
|
+
const args = (context, hierarchy) => ({ types, properties }) => {
|
9
|
+
const orderedByType = []
|
10
|
+
|
11
|
+
for (const type of types) {
|
12
|
+
let found = false
|
13
|
+
for (let index = 0; index < properties.length; ++index) {
|
14
|
+
const value = context[properties[index]]
|
15
|
+
if (!value) {
|
16
|
+
return false
|
17
|
+
}
|
18
|
+
if (hierarchy.isA(value.marker, type)) {
|
19
|
+
orderedByType.push(properties[index])
|
20
|
+
properties.splice(index, 1)
|
21
|
+
found = true
|
22
|
+
break
|
23
|
+
}
|
24
|
+
}
|
25
|
+
if (!found) {
|
26
|
+
return
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
if (orderedByType.length === 0) {
|
31
|
+
return
|
32
|
+
}
|
33
|
+
|
34
|
+
return orderedByType
|
35
|
+
}
|
36
|
+
|
37
|
+
const appendNoDups = (l1, l2) => {
|
38
|
+
for (const x of l2) {
|
39
|
+
if (l1.includes(x)) {
|
40
|
+
continue
|
41
|
+
}
|
42
|
+
l1.push(x)
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
const safeEquals = (v1, v2) => {
|
47
|
+
if (!deepEqual(stringify(v1), stringify(v2))) {
|
48
|
+
return false
|
49
|
+
}
|
50
|
+
return true
|
51
|
+
}
|
52
|
+
|
53
|
+
/*
|
54
|
+
const semanticsGenerate = (from, known) => {
|
55
|
+
const marker = from.marker
|
56
|
+
|
57
|
+
const sources = []
|
58
|
+
for (let key in Object.keys(from)) {
|
59
|
+
if (from[key].marker) {
|
60
|
+
sources.push(from[key])
|
61
|
+
}
|
62
|
+
}
|
63
|
+
const mappings = []
|
64
|
+
for (let key in Object.keys(to)) {
|
65
|
+
const source
|
66
|
+
if (to[keys].marker ==
|
67
|
+
}
|
68
|
+
|
69
|
+
return {
|
70
|
+
match: ({context}) => marker == marker,
|
71
|
+
apply: ({context}) => {
|
72
|
+
},
|
73
|
+
}
|
74
|
+
}
|
75
|
+
*/
|
76
|
+
|
77
|
+
const hashIndexesGet = (hash, indexes) => {
|
78
|
+
let value = hash
|
79
|
+
for (const i of indexes) {
|
80
|
+
value = value[i]
|
81
|
+
}
|
82
|
+
return value
|
83
|
+
}
|
84
|
+
|
85
|
+
const hashIndexesSet = (hash, indexes, value) => {
|
86
|
+
let currentValue = hash
|
87
|
+
for (const i of indexes.slice(0, -1)) {
|
88
|
+
if (!currentValue[i]) {
|
89
|
+
currentValue[i] = {}
|
90
|
+
}
|
91
|
+
currentValue = currentValue[i]
|
92
|
+
}
|
93
|
+
currentValue[indexes[indexes.length - 1]] = value
|
94
|
+
}
|
95
|
+
|
96
|
+
const translationMapping = (from, to) => {
|
97
|
+
const mappings = []
|
98
|
+
for (const fkey of Object.keys(from)) {
|
99
|
+
if (from[fkey].value) {
|
100
|
+
let found = false
|
101
|
+
const todo = Object.keys(to).map((key) => [key])
|
102
|
+
while (!found) {
|
103
|
+
const tkey = todo.shift()
|
104
|
+
const tvalue = hashIndexesGet(to, tkey)
|
105
|
+
const fvalue = hashIndexesGet(from, [fkey])
|
106
|
+
if (fvalue.value === tvalue.value) {
|
107
|
+
mappings.push({ from: [fkey], to: tkey })
|
108
|
+
found = true
|
109
|
+
break
|
110
|
+
} else {
|
111
|
+
if (typeof tvalue !== 'string' && typeof tvalue !== 'number') {
|
112
|
+
for (const key of Object.keys(tvalue)) {
|
113
|
+
todo.push(tkey.concat(key))
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
return mappings
|
121
|
+
}
|
122
|
+
|
123
|
+
const normalizeGenerator = (generator) => {
|
124
|
+
if (Array.isArray(generator)) {
|
125
|
+
if (generator.length === 2) {
|
126
|
+
return { match: generator[0], apply: generator[1] }
|
127
|
+
} else {
|
128
|
+
return { match: generator[0], apply: generator[1], uuid: generator[2] }
|
129
|
+
}
|
130
|
+
}
|
131
|
+
return generator
|
132
|
+
}
|
133
|
+
|
134
|
+
const normalizeSemantic = (semantic) => {
|
135
|
+
if (Array.isArray(semantic)) {
|
136
|
+
if (semantic.length === 2) {
|
137
|
+
return { match: semantic[0], apply: semantic[1] }
|
138
|
+
} else {
|
139
|
+
return { match: semantic[0], apply: semantic[1], uuid: semantic[2] }
|
140
|
+
}
|
141
|
+
}
|
142
|
+
return semantic
|
143
|
+
}
|
144
|
+
|
145
|
+
const isArray = (value) => {
|
146
|
+
return (!!value) && (value.constructor === Array)
|
147
|
+
}
|
148
|
+
|
149
|
+
const isObject = (value) => {
|
150
|
+
return (!!value) && (value.constructor === Object)
|
151
|
+
}
|
152
|
+
|
153
|
+
const isCompound = (value) => {
|
154
|
+
return isArray(value) || isObject(value)
|
155
|
+
}
|
156
|
+
|
157
|
+
nextCallId = 0
|
158
|
+
nextContextId = 0
|
159
|
+
|
160
|
+
class InitCalls {
|
161
|
+
|
162
|
+
constructor() {
|
163
|
+
this.nextCallId = 0
|
164
|
+
this.nextContextId = 0
|
165
|
+
this.stack = []
|
166
|
+
}
|
167
|
+
|
168
|
+
start() {
|
169
|
+
return this.nextCallId
|
170
|
+
}
|
171
|
+
|
172
|
+
next() {
|
173
|
+
// this.nextContextId += 1
|
174
|
+
this.nextContextId += 1
|
175
|
+
}
|
176
|
+
|
177
|
+
push() {
|
178
|
+
this.nextCallId += 1
|
179
|
+
// this.nextCallId += 1
|
180
|
+
// this.stack.push(this.nextCallId)
|
181
|
+
this.stack.push(this.nextCallId)
|
182
|
+
const calls = this.stack.map( (call) => `call${call}` )
|
183
|
+
// return `Context#${this.nextContextId}: ${calls}`
|
184
|
+
return `Context#${nextContextId}: ${calls}`
|
185
|
+
}
|
186
|
+
|
187
|
+
current() {
|
188
|
+
return `call${this.stack[this.stack.length-1]}`
|
189
|
+
}
|
190
|
+
|
191
|
+
touch(context) {
|
192
|
+
if (!context.touchedBy) {
|
193
|
+
context.touchedBy = []
|
194
|
+
}
|
195
|
+
if (!context.touchedBy.includes( this.current() )) {
|
196
|
+
context.touchedBy.push( this.current() )
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
pop() {
|
201
|
+
this.stack.pop()
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
const hashCode = (str) => {
|
206
|
+
var hash = 0, i, ch;
|
207
|
+
if (str.length === 0) return hash;
|
208
|
+
for (i = 0; i < str.length; i++) {
|
209
|
+
ch = str.charCodeAt(i);
|
210
|
+
hash = ((hash << 5) - hash) + ch;
|
211
|
+
hash |= 0; // Convert to 32bit integer
|
212
|
+
}
|
213
|
+
return hash;
|
214
|
+
};
|
215
|
+
|
216
|
+
module.exports = { args, safeEquals, appendNoDups, hashIndexesGet, hashIndexesSet, translationMapping, normalizeGenerator, normalizeSemantic, isArray, isObject, isCompound, InitCalls, hashCode }
|
package/src/semantics.js
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
const { args: contextArgs, normalizeGenerator, normalizeSemantic } = require('./helpers')
|
2
|
+
const Lines = require('../lines')
|
3
|
+
const sortJson = require('sort-json')
|
4
|
+
const helpers = require('./helpers')
|
5
|
+
|
6
|
+
class Semantic {
|
7
|
+
// constructor ({match, apply, uuid, index, km, notes}) {
|
8
|
+
constructor (semantic) {
|
9
|
+
semantic = normalizeSemantic(semantic)
|
10
|
+
const { match, apply, uuid, index, km, notes, priority, debug, where } = semantic
|
11
|
+
this.matcher = match
|
12
|
+
this._apply = apply
|
13
|
+
this.uuid = uuid
|
14
|
+
this.index = index
|
15
|
+
this.km = km
|
16
|
+
this.priority = priority || 0
|
17
|
+
this.notes = notes
|
18
|
+
this.callId = debug
|
19
|
+
this.where = where
|
20
|
+
}
|
21
|
+
|
22
|
+
toLabel () {
|
23
|
+
const where = (this.where) ? `where: "${this.where}"` : ''
|
24
|
+
if (!this.km) {
|
25
|
+
return where;
|
26
|
+
}
|
27
|
+
return `KM '${this.km}' ordinal: ${this.index} ${where}`
|
28
|
+
}
|
29
|
+
|
30
|
+
toString () {
|
31
|
+
return `Semantic(${this.matcher}, ${this._apply})`
|
32
|
+
}
|
33
|
+
|
34
|
+
getAPI (config) {
|
35
|
+
if (config && config.getAPI) {
|
36
|
+
return config.getAPI(this.uuid)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
getAPIs (config) {
|
41
|
+
if (config && config._api && config._api.multiApi) {
|
42
|
+
return config._api.apis
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
matches (baseArgs, context, options = {}) {
|
47
|
+
const hierarchy = baseArgs.hierarchy
|
48
|
+
const config = baseArgs.config
|
49
|
+
const objects = baseArgs.getObjects(this.uuid)
|
50
|
+
// return this.matcher(Object.assign({}, argsBase, {args: contextArgs(context, hierarchy), objects: objects, global: objects, context: context, hierarchy: hierarchy, api: this.getAPI(config)})
|
51
|
+
const callId = baseArgs.calls.current()
|
52
|
+
const moreArgs = {
|
53
|
+
uuid: this.uuid,
|
54
|
+
args: contextArgs(context, hierarchy),
|
55
|
+
objects: objects,
|
56
|
+
global: objects,
|
57
|
+
context: context,
|
58
|
+
// hierarchy: hierarchy,
|
59
|
+
callId,
|
60
|
+
api: this.getAPI(config),
|
61
|
+
apis: this.getAPIs(config)
|
62
|
+
}
|
63
|
+
const args = Object.assign({}, baseArgs, moreArgs)
|
64
|
+
|
65
|
+
const matches = this.matcher(args)
|
66
|
+
if (matches && (options.debug || {}).match
|
67
|
+
||
|
68
|
+
callId == this.callId) {
|
69
|
+
debugger; // next line is the matcher
|
70
|
+
this.matcher(args)
|
71
|
+
}
|
72
|
+
return matches
|
73
|
+
}
|
74
|
+
|
75
|
+
apply (baseArgs, context, s, log, options = {}) {
|
76
|
+
const { hierarchy, config, response } = baseArgs
|
77
|
+
const objects = baseArgs.getObjects(this.uuid)
|
78
|
+
if (!log) {
|
79
|
+
console.trace()
|
80
|
+
throw 'log is a required argument'
|
81
|
+
}
|
82
|
+
const contextPrime = Object.assign({}, context)
|
83
|
+
let n = (id) => id
|
84
|
+
if (config && 'nsToString' in config) {
|
85
|
+
n = (id) => config.nsToString(id)
|
86
|
+
}
|
87
|
+
const callId = baseArgs.calls.current()
|
88
|
+
const moreArgs = {
|
89
|
+
uuid: this.uuid,
|
90
|
+
callId,
|
91
|
+
args: contextArgs(context, hierarchy),
|
92
|
+
objects,
|
93
|
+
log,
|
94
|
+
global: objects,
|
95
|
+
n,
|
96
|
+
context: contextPrime,
|
97
|
+
uuid: this.uuid,
|
98
|
+
// config,
|
99
|
+
response,
|
100
|
+
api: this.getAPI(config),
|
101
|
+
apis: this.getAPIs(config)
|
102
|
+
}
|
103
|
+
const args = Object.assign({}, baseArgs, moreArgs)
|
104
|
+
if ((options.debug || {}).apply
|
105
|
+
||
|
106
|
+
callId == this.callId) {
|
107
|
+
debugger;
|
108
|
+
}
|
109
|
+
this._apply(args)
|
110
|
+
return contextPrime
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
class Semantics {
|
115
|
+
constructor (semantics, logs = []) {
|
116
|
+
semantics = semantics.map((semantic) => {
|
117
|
+
if (semantic instanceof Semantic) {
|
118
|
+
return semantic
|
119
|
+
} else {
|
120
|
+
return new Semantic(semantic)
|
121
|
+
}
|
122
|
+
})
|
123
|
+
|
124
|
+
const priorityToSemantics = {}
|
125
|
+
const add = (priority, value) => {
|
126
|
+
if (!priorityToSemantics[priority]) {
|
127
|
+
priorityToSemantics[priority] = []
|
128
|
+
}
|
129
|
+
priorityToSemantics[priority].push(value)
|
130
|
+
}
|
131
|
+
for (const semantic of semantics) {
|
132
|
+
const priority = semantic.priority
|
133
|
+
add(priority, semantic)
|
134
|
+
}
|
135
|
+
this.semantics = []
|
136
|
+
for (const key of Object.keys(priorityToSemantics).sort()) {
|
137
|
+
this.semantics = this.semantics.concat(priorityToSemantics[key])
|
138
|
+
}
|
139
|
+
|
140
|
+
this.logs = logs
|
141
|
+
// map ordinal to number of calls
|
142
|
+
this.calls = {}
|
143
|
+
};
|
144
|
+
|
145
|
+
getMostCalled() {
|
146
|
+
let maxOrdinal = 0
|
147
|
+
let maxCounter = 0
|
148
|
+
for (let ordinal in this.calls) {
|
149
|
+
const counter = this.calls[ordinal]
|
150
|
+
if (counter > maxCounter) {
|
151
|
+
maxOrdinal = ordinal
|
152
|
+
maxCounter = counter
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
return this.semantics[maxOrdinal]
|
157
|
+
}
|
158
|
+
|
159
|
+
applyToContext (args, context, options) {
|
160
|
+
// let context_prime = {}
|
161
|
+
if (!(context instanceof Array || context instanceof Object)) {
|
162
|
+
return context
|
163
|
+
}
|
164
|
+
|
165
|
+
const config = args.config
|
166
|
+
let contextPrime = Object.assign({}, context)
|
167
|
+
const s = (context, options) => this.apply(args, context, options)
|
168
|
+
let applied = false
|
169
|
+
const stack = args.calls.push()
|
170
|
+
let counter = 0;
|
171
|
+
for (const isemantic in this.semantics) {
|
172
|
+
const semantic = this.semantics[isemantic]
|
173
|
+
if (semantic.matches(args, context, options)) {
|
174
|
+
if (!this.calls[counter]) {
|
175
|
+
this.calls[counter] = 0;
|
176
|
+
}
|
177
|
+
this.calls[counter] += 1;
|
178
|
+
const log = (message) => { this.logs.push(message) }
|
179
|
+
try {
|
180
|
+
contextPrime = semantic.apply(args, context, s, log, options)
|
181
|
+
} catch( e ) {
|
182
|
+
contextPrime = null
|
183
|
+
let errorMessage
|
184
|
+
if (e.stack && e.message) {
|
185
|
+
errorMessage = `Error applying semantics '${semantic.notes}'. Error is ${e.toString()} stack is ${e.stack}. Semantic is ${semantic.toString()}`
|
186
|
+
} else if (e.error) {
|
187
|
+
errorMessage = `Error applying semantics '${semantic.notes}'. Error is ${e.error.join()}. Semantic is ${semantic.toString()}`
|
188
|
+
} else {
|
189
|
+
errorMessage = e.toString();
|
190
|
+
}
|
191
|
+
|
192
|
+
const widths = [10, 10, 90]
|
193
|
+
const lines = new Lines(widths)
|
194
|
+
lines.setElement(0, 0, 'Semantic')
|
195
|
+
const source = `${semantic.km}/#${semantic.index}`
|
196
|
+
lines.setElement(0, 2, `ERROR while applying (${source}) ${semantic.toLabel()}`)
|
197
|
+
lines.newRow()
|
198
|
+
lines.setElement(0, 2, semantic.toString())
|
199
|
+
lines.newRow()
|
200
|
+
lines.setElement(0, 1, 'TO')
|
201
|
+
lines.setElement(0, 2, `(HASHCODE ${helpers.hashCode(JSON.stringify(sortJson(context, { depth: 25 })))})`)
|
202
|
+
lines.setElement(1, 2, JSON.stringify(sortJson(context, { depth: 25 }), null, 2))
|
203
|
+
lines.newRow()
|
204
|
+
lines.setElement(0, 1, 'STACK')
|
205
|
+
lines.setElement(0, 2, stack)
|
206
|
+
lines.newRow()
|
207
|
+
lines.setElement(0, 1, 'DEBUG')
|
208
|
+
lines.setElement(0, 2, `To debug this use args.callId == '${args.calls.current()}'`)
|
209
|
+
lines.newRow()
|
210
|
+
lines.setElement(0, 1, 'ERROR')
|
211
|
+
lines.setElement(0, 2, errorMessage)
|
212
|
+
this.logs.push(lines.toString())
|
213
|
+
const message = `ERROR while applying (${source}) ${semantic.toLabel()}\n to\n ${JSON.stringify(context, null, 2)}.\n${errorMessage}'`
|
214
|
+
// this.logs.push(message)
|
215
|
+
// return [message]
|
216
|
+
args.calls.pop()
|
217
|
+
throw { error: [message], logs: this.logs }
|
218
|
+
}
|
219
|
+
args.calls.touch(contextPrime)
|
220
|
+
// this.logs.push(`Semantics: applied ${semantic.toString()}\n to\n ${JSON.stringify(context)}\n the result was ${JSON.stringify(contextPrime)}\n`)
|
221
|
+
if (((config || {}).config || {}).debug) {
|
222
|
+
const widths = [10, 10, 90]
|
223
|
+
const lines = new Lines(widths)
|
224
|
+
lines.setElement(0, 0, 'Semantic')
|
225
|
+
if (semantic.index > -1 && semantic.km) {
|
226
|
+
// lines.setElement(0, 2, `KM '${semantic.km}' ordinal: ${semantic.index} ${ semantic.notes ? semantic.nodes : '' }`)
|
227
|
+
lines.setElement(0, 2, semantic.toLabel())
|
228
|
+
}
|
229
|
+
lines.newRow()
|
230
|
+
lines.setElement(0, 1, 'APPLIED')
|
231
|
+
lines.setElement(0, 2, semantic.toLabel())
|
232
|
+
lines.newRow()
|
233
|
+
lines.setElement(0, 2, semantic.toString())
|
234
|
+
lines.newRow()
|
235
|
+
lines.setElement(0, 1, 'TO')
|
236
|
+
lines.setElement(0, 2, `(HASHCODE ${helpers.hashCode(JSON.stringify(sortJson(context, { depth: 25 })))})`)
|
237
|
+
lines.setElement(1, 2, JSON.stringify(sortJson(context, { depth: 25 }), null, 2))
|
238
|
+
lines.newRow()
|
239
|
+
lines.setElement(0, 1, 'STACK')
|
240
|
+
lines.setElement(0, 2, stack)
|
241
|
+
lines.newRow()
|
242
|
+
lines.setElement(0, 1, 'DEBUG')
|
243
|
+
lines.setElement(0, 2, `To debug this use args.callId == '${args.calls.current()}'`)
|
244
|
+
lines.newRow()
|
245
|
+
lines.setElement(0, 1, 'RESULT')
|
246
|
+
lines.setElement(0, 2, `(HASHCODE ${helpers.hashCode(JSON.stringify(sortJson(contextPrime, { depth: 25 })))})`)
|
247
|
+
lines.setElement(1, 2, JSON.stringify(contextPrime, null, 2))
|
248
|
+
this.logs.push(lines.toString())
|
249
|
+
}
|
250
|
+
applied = true
|
251
|
+
if (contextPrime.cascade) {
|
252
|
+
contextPrime.cascade = false
|
253
|
+
} else {
|
254
|
+
break
|
255
|
+
}
|
256
|
+
}
|
257
|
+
counter += 1
|
258
|
+
}
|
259
|
+
args.calls.pop()
|
260
|
+
if (!applied && ((config || {}).config || {}).debug) {
|
261
|
+
const widths = [10, 10, 90]
|
262
|
+
const lines = new Lines(widths)
|
263
|
+
lines.setElement(0, 0, 'Semantic')
|
264
|
+
lines.setElement(0, 2, 'No semantic applied')
|
265
|
+
lines.newRow()
|
266
|
+
lines.setElement(0, 1, 'TO')
|
267
|
+
lines.setElement(0, 2, `(HASHCODE ${helpers.hashCode(JSON.stringify(sortJson(context, { depth: 25 })))})`)
|
268
|
+
lines.setElement(1, 2, JSON.stringify(sortJson(context, { depth: 25 }), null, 2))
|
269
|
+
this.logs.push(lines.toString())
|
270
|
+
}
|
271
|
+
return contextPrime
|
272
|
+
}
|
273
|
+
|
274
|
+
applyToContexts (args, contexts, options) {
|
275
|
+
const contextsPrime = []
|
276
|
+
contexts.forEach((context) => {
|
277
|
+
contextsPrime.push(this.applyToContext(args, context, options))
|
278
|
+
})
|
279
|
+
return contextsPrime
|
280
|
+
}
|
281
|
+
|
282
|
+
apply (args, context, options) {
|
283
|
+
if (Array.isArray(context)) {
|
284
|
+
return this.applyToContexts(args, context, options)
|
285
|
+
} else if (context instanceof Object) {
|
286
|
+
return this.applyToContext(args, context, options)
|
287
|
+
} else {
|
288
|
+
return context
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
module.exports = { Semantic, Semantics, normalizeGenerator }
|
package/src/unflatten.js
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
const _ = require('lodash')
|
2
|
+
const deepEqual = require('deep-equal')
|
3
|
+
|
4
|
+
const groupBy = (property, list) => {
|
5
|
+
const groups = {}
|
6
|
+
for (const element of list) {
|
7
|
+
if (!groups[element[property]]) {
|
8
|
+
groups[element[property]] = []
|
9
|
+
}
|
10
|
+
groups[element[property]].push(element)
|
11
|
+
}
|
12
|
+
return groups
|
13
|
+
}
|
14
|
+
|
15
|
+
const toList = (value) => {
|
16
|
+
if (value.marker === 'list') {
|
17
|
+
return value
|
18
|
+
} else {
|
19
|
+
return {
|
20
|
+
marker: 'list',
|
21
|
+
types: Array.from(new Set((value.types || []).concat(['list', value.marker]))),
|
22
|
+
value: [value]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
const concatLists = (l1, l2) => {
|
28
|
+
l1 = toList(l1)
|
29
|
+
l2 = toList(l2)
|
30
|
+
return {
|
31
|
+
marker: 'list',
|
32
|
+
types: Array.from(new Set(l1.types.concat(l2.types))),
|
33
|
+
value: Array.from(new Set(l1.value.concat(l2.value)))
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
const findPropertyWithManyValues = (contexts, properties) => {
|
38
|
+
for (const property of properties) {
|
39
|
+
if (new Set(contexts.map( (context) => context[property] )).size == 1) {
|
40
|
+
return property
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
/*
|
46
|
+
class JSONSet {
|
47
|
+
constructor(list = []) {
|
48
|
+
this.members = []
|
49
|
+
for (const element of list) {
|
50
|
+
this.add(element)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
toList(element) {
|
55
|
+
return this.list
|
56
|
+
}
|
57
|
+
|
58
|
+
add(element) {
|
59
|
+
if (!this.has(element)) {
|
60
|
+
this.list.push(element)
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
has(searchFor) {
|
65
|
+
for (const element of this.list) {
|
66
|
+
if (!deepEqual(searchFor, element) {
|
67
|
+
return true
|
68
|
+
}
|
69
|
+
}
|
70
|
+
return false
|
71
|
+
}
|
72
|
+
}
|
73
|
+
*/
|
74
|
+
|
75
|
+
// x want y and z OR A
|
76
|
+
// x and y want z B
|
77
|
+
// x wants and likes y C
|
78
|
+
|
79
|
+
const canonicalDefault = (value) => {
|
80
|
+
if (!value?.marker) {
|
81
|
+
return value;
|
82
|
+
}
|
83
|
+
return { marker: value.marker, value: value.value, word: value.wordi, types: value.types }
|
84
|
+
}
|
85
|
+
|
86
|
+
// if properties null then check the contexts for unflatten property
|
87
|
+
const unflatten = (contexts, properties, canonical = canonicalDefault) => {
|
88
|
+
const grouped = {}
|
89
|
+
for (let context of contexts) {
|
90
|
+
if (!grouped[context.marker]) {
|
91
|
+
grouped[context.marker] = []
|
92
|
+
}
|
93
|
+
grouped[context.marker].push(context)
|
94
|
+
}
|
95
|
+
let results = []
|
96
|
+
for (let key in grouped) {
|
97
|
+
results = results.concat(unflattenHelper(grouped[key], properties || grouped[key][0].unflatten, canonical))
|
98
|
+
}
|
99
|
+
return results
|
100
|
+
}
|
101
|
+
|
102
|
+
// properties -> order of preference for grouping values, for example B,A,c
|
103
|
+
const unflattenHelper = (contexts, properties, canonical) => {
|
104
|
+
if (contexts.length < 2) {
|
105
|
+
return contexts
|
106
|
+
}
|
107
|
+
if (!properties) {
|
108
|
+
return contexts
|
109
|
+
}
|
110
|
+
const groupedByMarker = groupBy('marker', contexts)
|
111
|
+
const unflats = []
|
112
|
+
for (const key in groupedByMarker) {
|
113
|
+
const grouping = groupedByMarker[key]
|
114
|
+
const groupingProp = findPropertyWithManyValues(contexts, properties)
|
115
|
+
const first = _.cloneDeep(grouping[0])
|
116
|
+
for (const next of grouping.slice(1)) {
|
117
|
+
for (const prop in next) {
|
118
|
+
if (!deepEqual(canonical(next[prop]), canonical(first[prop]))) {
|
119
|
+
first[prop] = concatLists(first[prop], next[prop])
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
unflats.push(first)
|
124
|
+
}
|
125
|
+
return unflats
|
126
|
+
}
|
127
|
+
|
128
|
+
module.exports = { groupBy, unflatten }
|