theprogrammablemind
Version:
466 lines (420 loc) • 11.3 kB
JavaScript
const deepEqual = require('deep-equal')
const stringify = require('json-stable-stringify')
function where (goUp = 2) {
const e = new Error()
const regexForm1 = /\((.*):(\d+):(\d+)\)$/
const regexForm2 = /at (.*):(\d+):(\d+)$/
const lines = e.stack.split('\n')
let line
let match
for (line of lines.slice(1)) {
// if (!(line.includes('config.js:') || line.includes('client.js:') || line.includes('<anonymous>'))) {
if (!(line.includes('config.js:') || line.includes('client.js:') || line.includes('helpers.js:'))) {
match = regexForm1.exec(line) || regexForm2.exec(line)
if (!match) {
continue
}
break
}
}
// const line = e.stack.split("\n")[goUp];
// const match = regexForm1.exec(line) || regexForm2.exec(line)
if (match) {
return `${match[1]}:${match[2]}`
} else {
return 'running in browser or in an async call so the stack is broken.'
}
}
function suggestAssociationsFix (esummary, asummary) {
for (let isummary = 0; isummary < esummary.length; ++isummary) {
if (!deepEqual(esummary[isummary], asummary[isummary])) {
return esummary[isummary].operators
}
}
return []
}
function suggestAssociationsFixFromSummaries (esummaries, asummaries) {
for (let isummaries = 0; isummaries < esummaries.length; ++isummaries) {
const esummary = esummaries[isummaries].summaries
const asummary = asummaries[isummaries].summaries
for (let isummary = 0; isummary < esummary.length; ++isummary) {
if (!deepEqual(esummary[isummary], asummary[isummary])) {
return esummary[isummary].operators
}
}
}
return []
}
function w (func) {
func.where = where(3)
return func
}
// properties - the properties that correspond to types
// types - the expected types of the properties
// returns list of properties found matching order of types
const args = (context, hierarchy) => ({ types, properties }) => {
const orderedByType = []
for (const type of types) {
let found = false
for (let index = 0; index < properties.length; ++index) {
const value = context[properties[index]]
if (!value) {
return false
}
if (hierarchy.isA(value.marker, type)) {
orderedByType.push(properties[index])
properties.splice(index, 1)
found = true
break
}
}
if (!found) {
return
}
}
if (orderedByType.length === 0) {
return
}
return orderedByType
}
const appendNoDups = (l1, l2) => {
for (const x of l2) {
if (l1.includes(x)) {
continue
}
l1.push(x)
}
}
const safeNoDups = (list) => {
noDups = []
for (const element of list) {
if (!noDups.find((e) => safeEquals(e, element))) {
noDups.push(element)
}
}
return noDups
}
const safeEquals = (v1, v2) => {
if (typeof v1 !== typeof v2) {
return false
}
const type = typeof v1
if (type == 'number' || type == 'string') {
return v1 == v2
} else if (type == 'function') {
return v1.toString() == v2.toString()
} else if (v1 == undefined || v2 == undefined) {
return v1 == v2
} else {
if (v1.length != v2.length) {
return false
}
for (const key in v1) {
if (!safeEquals(v1[key], v2[key])) {
return false
}
}
return true
}
}
/*
const semanticsGenerate = (from, known) => {
const marker = from.marker
const sources = []
for (let key in Object.keys(from)) {
if (from[key].marker) {
sources.push(from[key])
}
}
const mappings = []
for (let key in Object.keys(to)) {
const source
if (to[keys].marker ==
}
return {
match: ({context}) => marker == marker,
apply: ({context}) => {
},
}
}
*/
const hashIndexesGet = (hash, indexes) => {
let value = hash
for (const i of indexes) {
value = value[i]
}
return value
}
const hashIndexesSet = (hash, indexes, value) => {
let currentValue = hash
for (const i of indexes.slice(0, -1)) {
if (!currentValue[i]) {
currentValue[i] = {}
}
currentValue = currentValue[i]
}
currentValue[indexes[indexes.length - 1]] = value
}
const translationMapping = (from, to) => {
const mappings = []
for (const fkey of Object.keys(from)) {
if (from[fkey].value) {
let found = false
const todo = Object.keys(to).map((key) => [key])
while (!found) {
const tkey = todo.shift()
const tvalue = hashIndexesGet(to, tkey)
const fvalue = hashIndexesGet(from, [fkey])
if (fvalue.value === tvalue.value) {
mappings.push({ from: [fkey], to: tkey })
found = true
break
} else {
if (typeof tvalue !== 'string' && typeof tvalue !== 'number') {
for (const key of Object.keys(tvalue)) {
todo.push(tkey.concat(key))
}
}
}
}
}
}
return mappings
}
const normalizeGenerator = (generator) => {
if (Array.isArray(generator)) {
if (generator.length === 2) {
return { match: generator[0], apply: generator[1] }
} else {
return { match: generator[0], apply: generator[1], uuid: generator[2] }
}
}
return generator
}
const normalizeSemantic = (semantic) => {
if (Array.isArray(semantic)) {
if (semantic.length === 2) {
return { match: semantic[0], apply: semantic[1] }
} else {
return { match: semantic[0], apply: semantic[1], uuid: semantic[2] }
}
}
return semantic
}
const isArray = (value) => {
return (!!value) && (value.constructor === Array)
}
const isObject = (value) => {
return (!!value) && (value.constructor === Object)
}
const isCompound = (value) => {
return isArray(value) || isObject(value)
}
class InitCalls {
constructor (name) {
this.nextCallId = 0
this.nextContextId = 0
this.stack = []
this.name = name
}
start () {
return this.nextCallId
}
next () {
// this.nextContextId += 1
this.nextContextId += 1
}
push () {
this.nextCallId += 1
// this.nextCallId += 1
// this.stack.push(this.nextCallId)
this.stack.push(this.nextCallId)
// TODO put the nextContextId in the context for debugging
const calls = this.stack.map((call) => `${this.name}#call${call}`)
// return `Context#${this.nextContextId}: ${calls}`
return `Context#${this.nextContextId}: ${calls}`
}
current () {
return `${this.name}#call${this.stack[this.stack.length - 1]}`
}
touch (context) {
if (!context.touchedBy) {
context.touchedBy = []
}
if (!context.touchedBy.includes(this.current())) {
context.touchedBy.push(this.current())
}
}
pop () {
this.stack.pop()
}
}
const hashCode = (str) => {
let hash = 0; let i; let ch
if (str.length === 0) return hash
for (i = 0; i < str.length; i++) {
ch = str.charCodeAt(i)
hash = ((hash << 5) - hash) + ch
hash |= 0 // Convert to 32bit integer
}
return hash
}
const sortJson = (json) => {
return json
}
const validProps = (valids, object, type) => {
for (const prop of Object.keys(object)) {
let okay = false
for (valid of valids) {
if (typeof valid === 'string') {
okay = prop == valid
} else {
okay = prop.match(valid)
}
if (okay) {
break
}
}
if (!okay) {
throw new Error(`Unknown property "${prop}" in the ${type}. Valid properties are ${valids.join(', ')}. The ${type} is ${JSON.stringify(object)}`)
}
}
}
const mapInPlace = (list, fn) => {
for (let i = 0; i < list.length; ++i) {
list[i] = fn(list[i])
}
}
const updateQueries = (queryOrConfig) => {
if (typeof queryOrConfig === 'string' || queryOrConfig.query) {
return queryOrConfig
} else if (typeof queryOrConfig === 'function') {
return { apply: queryOrConfig.toString() }
} else {
const config = queryOrConfig
return functionsToStrings(config)
}
}
const functionsToStrings = (config) => {
config = { ...config }
defToStrings = (def) => {
if (def.apply) {
// return { ...def, match: def.match.toString(), apply: def.apply.toString() }
return { match: def.match.toString(), apply: def.apply.toString() }
} else {
return [def[0].toString(), def[1].toString()]
}
}
if (config.generators) {
config.generators = config.generators.map(defToStrings)
}
if (config.semantics) {
config.semantics = config.semantics.map(defToStrings)
}
if (config.bridges) {
config.bridges = config.bridges.map((bridge) => {
bridge = { ...bridge }
if (bridge.generator) {
bridge.generator = bridge.generator.toString()
}
if (bridge.generators) {
bridge.generators = bridge.generators.map(defToStrings)
}
if (bridge.generatorp) {
bridge.generatorp = bridge.generatorp.toString()
}
if (bridge.generatorr) {
bridge.generatorr = bridge.generatorr.toString()
}
if (bridge.semantic) {
bridge.semantic = bridge.semantic.toString()
}
if (bridge.semantics) {
bridge.semantics = bridge.semantics.toString()
}
if (bridge.evaluator) {
bridge.evaluator = bridge.evaluator.toString()
}
if (bridge.evaluators) {
bridge.evaluators = bridge.evaluators.toString()
}
return bridge
})
}
return config
}
const ecatch = (where, call) => {
try {
return call()
} catch (e) {
throw new Error(`${where} ${e.stack}`)
}
}
const equalKey = (key1, key2) => {
return key1[0] == key2[0] && key1[1] == key2[1]
}
// matches for { context: ..., [ordered], choose: ... } exactely OR
// [ <id1>, <id2> ] - where id1 is chosen
const subPriority = (sub, sup) => {
if (Array.isArray(sub)) {
const subChoosen = sub[0]
const subOther = sub[1]
const hasChoosen = sup.choose.find((index) => equalKey(sup.context[index], subChoosen)) != undefined
const hasOtherChosen = sup.choose.find((index) => equalKey(sup.context[index], subOther)) != undefined
const hasOther = sup.context.find((other) => equalKey(other, subOther)) !== undefined
return !!(hasChoosen && hasOther) && !hasOtherChosen
}
if (!safeEquals([...sub.choose].sort(), [...sup.choose].sort())) {
return false
}
const choose = (priority) => {
const chosen = []
for (const i of priority.choose) {
chosen.push(priority.context[i])
}
return chosen
}
const chosen1 = choose(sub)
const chosen2 = choose(sup)
const sameId = (id1, id2) => id1[0] == id2[0] && id1[1] == id2[1]
// same length so only need one way
const missing1 = chosen1.find((id1) => !chosen2.find((id2) => sameId(id1, id2)))
if (missing1) {
return false
}
return true
}
const stableIds = {}
const stableId = (tag) => {
const id = stableIds[tag] || 1
stableIds[tag] = id + 1
return id
}
module.exports = {
stableId,
ecatch,
functionsToStrings,
updateQueries,
mapInPlace,
validProps,
args,
safeNoDups,
safeEquals,
appendNoDups,
hashIndexesGet,
hashIndexesSet,
translationMapping,
normalizeGenerator,
normalizeSemantic,
isArray,
isObject,
isCompound,
InitCalls,
hashCode,
sortJson,
subPriority,
where,
w,
suggestAssociationsFix,
suggestAssociationsFixFromSummaries
}