plugin-mini.mjs - <short description TODO> Copyright (C) 2022 Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/superdough.mjs This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.
9const languageLiteral = { 10 walk: (context) => ({ 11 enter: function (node, parent, prop, index) { 12 if (!isLanguageLiteral(node)) return; 13 context.miniLocations ??= []; 14 const { options, miniLocations } = context; 15 const { emitMiniLocations } = options; 16 const { name } = node.tag; 17 const language = getLanguages().get(name); 18 const code = node.quasi.quasis[0].value.raw; 19 const offset = node.quasi.start + 1; 20 if (emitMiniLocations) { 21 const locs = language.getLocations(code, offset); 22 miniLocations.push(...locs); 23 } 24 this.skip(); 25 return this.replace(languageWithLocation(name, code, offset)); 26 }, 27 }), 28}; 29 30const tidal = { 31 walk: (context) => ({ 32 enter: function (node, parent, prop, index) { 33 if (!isTemplateLiteral(node, 'tidal')) return; 34 context.miniLocations ??= []; 35 const { options, miniLocations } = context; 36 const { emitMiniLocations } = options; 37 const raw = node.quasi.quasis[0].value.raw; 38 const offset = node.quasi.start + 1; 39 if (emitMiniLocations) { 40 const stringLocs = collectHaskellMiniLocations(raw, offset); 41 miniLocations.push(...stringLocs); 42 } 43 this.skip(); 44 return this.replace(tidalWithLocation(raw, offset)); 45 }, 46 }), 47}; 48 49const backtick = { 50 walk: (context) => ({ 51 enter: function (node, parent, prop, index) { 52 if (!isBackTickString(node, parent)) return; 53 context.miniLocations ??= []; 54 const { options, input, miniDisableRanges, miniLocations } = context; 55 const { emitMiniLocations } = options; 56 if (isMiniDisabled(node.start, miniDisableRanges)) { 57 return; 58 } 59 const { quasis } = node; 60 const { raw } = quasis[0].value; 61 this.skip(); 62 emitMiniLocations && collectMiniLocations(raw, node, miniLocations, input); 63 return this.replace(miniWithLocation(raw, node)); 64 }, 65 }), 66}; 67 68const doublequotes = { 69 walk: (context) => ({ 70 enter: function (node, parent, prop, index) { 71 if (!isStringWithDoubleQuotes(node)) return; 72 context.miniLocations ??= []; 73 const { options, input, miniDisableRanges, miniLocations } = context; 74 const { emitMiniLocations } = options; 75 if (isMiniDisabled(node.start, miniDisableRanges)) { 76 return; 77 } 78 const { value } = node; 79 this.skip(); 80 emitMiniLocations && collectMiniLocations(value, node, miniLocations, input); 81 return this.replace(miniWithLocation(value, node)); 82 }, 83 }), 84}; 85 86function isLanguageLiteral(node) { 87 return node.type === 'TaggedTemplateExpression' && getLanguages().has(node.tag.name); 88} 89 90function languageWithLocation(name, value, offset) { 91 return { 92 type: 'CallExpression', 93 callee: { 94 type: 'Identifier', 95 name: name, 96 }, 97 arguments: [ 98 { type: 'Literal', value }, 99 { type: 'Literal', value: offset }, 100 ], 101 optional: false, 102 }; 103} 104 105function collectHaskellMiniLocations(haskellCode, offset) { 106 return haskellCode 107 .split('') 108 .reduce((acc, char, i) => { 109 if (char !== '"') { 110 return acc; 111 } 112 if (!acc.length || acc[acc.length - 1].length > 1) { 113 acc.push([i + 1]); 114 } else { 115 acc[acc.length - 1].push(i); 116 } 117 return acc; 118 }, []) 119 .map(([start, end]) => { 120 const miniString = haskellCode.slice(start, end); 121 return getLeafLocations(`"${miniString}"`, offset + start - 1); 122 }) 123 .flat(); 124} 125 126function isTemplateLiteral(node, value) { 127 return node.type === 'TaggedTemplateExpression' && node.tag.name === value; 128} 129 130function tidalWithLocation(value, offset) { 131 return { 132 type: 'CallExpression', 133 callee: { 134 type: 'Identifier', 135 name: 'tidal', 136 }, 137 arguments: [ 138 { type: 'Literal', value }, 139 { type: 'Literal', value: offset }, 140 ], 141 optional: false, 142 }; 143} 144 145function isBackTickString(node, parent) { 146 return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression'; 147} 148 149function isMiniDisabled(offset, miniDisableRanges) { 150 for (const [start, end] of miniDisableRanges) { 151 if (offset >= start && offset < end) { 152 return true; 153 } 154 } 155 return false; 156} 157 158function miniWithLocation(value, node) { 159 const { start: fromOffset } = node; 160 161 const minilang = getLanguages().get('minilang'); 162 let name = 'm'; 163 if (minilang && minilang.name) { 164 name = minilang.name; // name is expected to be exported from the package of the minilang 165 } 166 167 return { 168 type: 'CallExpression', 169 callee: { 170 type: 'Identifier', 171 name, 172 }, 173 arguments: [ 174 { type: 'Literal', value }, 175 { type: 'Literal', value: fromOffset }, 176 ], 177 optional: false, 178 }; 179} 180 181const collectMiniLocations = (value, node, miniLocations, input) => { 182 const minilang = getLanguages().get('minilang'); 183 if (minilang) { 184 const code = `[${value}]`; 185 const locs = minilang.getLocations(code, node.start); 186 miniLocations.push(...locs); 187 } else { 188 const leafLocs = getLeafLocations(`"${value}"`, node.start, input); 189 miniLocations.push(...leafLocs); 190 } 191}; 192 193function isStringWithDoubleQuotes(node, locations, code) { 194 if (node.type !== 'Literal') { 195 return false; 196 } 197 return node.raw[0] === '"'; 198} 199 200registerTranspilerPlugin([languageLiteral, tidal, backtick, doublequotes]);