grammar.js
1 import every from 'iter-tools-es/methods/every'; 2 import isString from 'iter-tools-es/methods/is-string'; 3 import { getOwnPropertySymbols, getPrototypeOf, objectEntries } from './object.js'; 4 import { OpenNodeTag, CloseNodeTag, NullTag } from './symbols.js'; 5 import { buildExpression } from './builders.js'; 6 import { buildEmbeddedObject } from '@bablr/agast-helpers/tree'; 7 8 export * from './decorators.js'; 9 10 const { getOwnPropertyNames, hasOwn } = Object; 11 12 const { isArray } = Array; 13 const isSymbol = (value) => typeof value === 'symbol'; 14 const isType = (value) => isString(value) || isSymbol(value); 15 16 export const notNull = (facade) => { 17 return facade && (facade.type !== null || facade.openTag.type !== NullTag); 18 }; 19 20 export const mapProductions = (fn, Grammar) => { 21 let { prototype } = Grammar; 22 23 class MappedGrammar extends Grammar {} 24 25 const mapped = MappedGrammar.prototype; 26 27 while (prototype && prototype !== Object.prototype) { 28 for (const key of [...getOwnPropertyNames(prototype), ...getOwnPropertySymbols(prototype)]) { 29 if (!hasOwn(mapped, key)) { 30 mapped[key] = fn(prototype[key], key); 31 } 32 } 33 prototype = getPrototypeOf(prototype); 34 } 35 36 return MappedGrammar; 37 }; 38 39 export function* generateProductions(Grammar) { 40 let { prototype } = Grammar; 41 42 while (prototype && prototype !== Object.prototype) { 43 for (const key of [...getOwnPropertyNames(prototype), ...getOwnPropertySymbols(prototype)]) { 44 let value = prototype[key]; 45 if (key !== 'constructor') yield [key, value]; 46 } 47 prototype = getPrototypeOf(prototype); 48 } 49 } 50 51 export const resolveLanguage = (context, language, path) => { 52 const { languages } = context; 53 if (isString(path)) { 54 if (language.canonicalURL === path) { 55 return language; 56 } else { 57 throw new Error('absolute path resolution not implemented'); 58 } 59 } 60 61 let l = language; 62 63 if (!l) { 64 throw new Error(); 65 } 66 67 let segments = isString(path) ? [path] : isArray(path) ? path : null; 68 69 if (path == null) { 70 return language; 71 } else { 72 for (const segment of segments) { 73 if (isString(l.dependencies[segment])) { 74 l = languages.get(l.dependencies[segment]); 75 } else { 76 l = l.dependencies[segment]; 77 } 78 } 79 } 80 81 return l; 82 }; 83 84 export const unresolveLanguage = (context, baseLanguage, absoluteLanguage) => { 85 if (absoluteLanguage == null || absoluteLanguage === baseLanguage.canonicalURL) { 86 return null; 87 } 88 89 for (const { 0: key, 1: value } of objectEntries(baseLanguage.dependencies)) { 90 if (value.canonicalURL === absoluteLanguage) { 91 return [key]; 92 } 93 } 94 95 throw new Error('Cannot currently unresolve nested deps'); 96 }; 97 98 export const explodeSubtypes = (aliases, exploded, types) => { 99 for (const type of types) { 100 const explodedTypes = aliases.get(type); 101 if (explodedTypes) { 102 for (const explodedType of explodedTypes) { 103 exploded.add(explodedType); 104 const subtypes = aliases.get(explodedType); 105 if (subtypes) { 106 explodeSubtypes(aliases, exploded, subtypes); 107 } 108 } 109 } 110 } 111 }; 112 113 export const buildCovers = (rawAliases) => { 114 const aliases = new Map(); 115 116 for (const alias of objectEntries(rawAliases)) { 117 if (!isType(alias[0])) throw new Error('alias[0] key must be a string or symbol'); 118 if (!isArray(alias[1])) throw new Error('alias[1] must be an array'); 119 if (!every(isType, alias[1])) throw new Error('alias[1] values must be strings or symbols'); 120 121 aliases.set(alias[0], new Set(alias[1])); 122 } 123 124 for (const [type, types] of aliases.entries()) { 125 explodeSubtypes(aliases, aliases.get(type), types); 126 } 127 128 return new Map(aliases); 129 }; 130 131 export const getProduction = (grammar, type) => { 132 return getPrototypeOf(grammar)[type]; 133 }; 134 135 const __buildDependentLanguages = (language, languages = new Map()) => { 136 languages.set(language.canonicalURL, language); 137 138 for (const dependentLanguage of Object.values(language.dependencies || {})) { 139 if (isString(dependentLanguage)) continue; 140 141 const { canonicalURL } = dependentLanguage; 142 if (languages.has(canonicalURL) && dependentLanguage !== languages.get(canonicalURL)) { 143 throw new Error(); 144 } 145 146 if (!languages.has(dependentLanguage)) { 147 __buildDependentLanguages(dependentLanguage, languages); 148 } 149 } 150 151 return languages; 152 }; 153 154 export const buildDependentLanguages = (language) => { 155 return __buildDependentLanguages(language); 156 }; 157 158 export const extendLanguage = (language, extension) => { 159 return { 160 ...language, 161 dependencies: extension.dependencies 162 ? { ...language.dependencies, ...extension.dependencies } 163 : language.dependencies, 164 canonicalURL: extension.canonicalURL || language.canonicalURL, 165 grammar: extension.grammar, 166 }; 167 }; 168 169 const arrayLast = (arr) => arr[arr.length - 1]; 170 171 export function* zipLanguages(tags, rootLanguage) { 172 const languages = [rootLanguage]; 173 174 for (const tag of tags) { 175 switch (tag.type) { 176 case OpenNodeTag: { 177 if (tag.value.language) { 178 const dependentLanguage = languages.dependencies[tag.value.language]; 179 180 if (!dependentLanguage) throw new Error('language was not a dependency'); 181 182 languages.push(dependentLanguage); 183 } 184 break; 185 } 186 187 case CloseNodeTag: { 188 if (tag.value.language !== arrayLast(languages).canonicalURL) { 189 languages.pop(); 190 } 191 break; 192 } 193 } 194 195 yield [tag, arrayLast(languages)]; 196 } 197 } 198 199 const safeShallowEmbed = (value) => { 200 if (typeof value !== 'object') { 201 return buildExpression(value); 202 } else { 203 return value; 204 } 205 }; 206 207 const __buildCall = (verb, ...args) => { 208 while (args.length && args[args.length - 1] === undefined) { 209 args.pop(); 210 } 211 212 return { verb, arguments: args }; 213 }; 214 215 export const eat = (matcher, value, options) => { 216 return __buildCall('eat', matcher, value, options); 217 }; 218 219 export const eatMatch = (matcher, value, options) => { 220 return __buildCall('eatMatch', matcher, value, options); 221 }; 222 223 export const match = (matcher, value, options) => { 224 return __buildCall('match', matcher, value, options); 225 }; 226 227 export const guard = (matcher, value, options) => { 228 return __buildCall('guard', matcher, value, options); 229 }; 230 231 export const holdForMatch = (matcher, value, options) => { 232 return __buildCall('holdForMatch', matcher, value, options); 233 }; 234 235 export const fail = () => { 236 return __buildCall('fail'); 237 }; 238 239 export const bindAttribute = (key, value) => { 240 return __buildCall('bindAttribute', key, value); 241 }; 242 243 export const write = (value) => { 244 return __buildCall('write', value); 245 }; 246 247 export const o = buildEmbeddedObject;