jevstrudel.git / packages / transpiler / plugin-mini.mjs

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/.

6import { getLeafLocations } from '@strudel/mini';
7import { getLanguages, registerTranspilerPlugin } from './transpiler.mjs';
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]);