mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-12 22:15:27 -04:00
refactor: localize, remove helpers file when there's no reuse
This commit is contained in:
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
helpers.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/>.
|
||||
*/
|
||||
import escodegen from 'escodegen';
|
||||
import { walk } from 'estree-walker';
|
||||
|
||||
let languages = new Map();
|
||||
// config = { getLocations: (code: string, offset?: number) => number[][] }
|
||||
// see mondough.mjs for example use
|
||||
// the language will kick in when the code contains a template literal of type
|
||||
// example: mondo`...` will use language of type "mondo"
|
||||
// TODO: refactor tidal.mjs to use this
|
||||
export function registerLanguage(type, config) {
|
||||
languages.set(type, config);
|
||||
}
|
||||
export function getLanguages() {
|
||||
return languages;
|
||||
}
|
||||
|
||||
export function withAwait(node) {
|
||||
return {
|
||||
type: 'AwaitExpression',
|
||||
argument: node,
|
||||
};
|
||||
}
|
||||
|
||||
export function genExprSource(expr) {
|
||||
return escodegen.generate(expr, { format: { semicolons: false } });
|
||||
}
|
||||
|
||||
export function extractPatternPlaceholders(expr) {
|
||||
const templateExpr = cloneNode(expr);
|
||||
const parents = new Map();
|
||||
const targets = [];
|
||||
|
||||
walk(templateExpr, {
|
||||
enter(node, parent, prop, index) {
|
||||
parents.set(node, { parent, prop, index });
|
||||
const patternExpr = getStrudelPatternExpr(node);
|
||||
if (patternExpr) {
|
||||
targets.push({ node, patternExpr });
|
||||
this.skip();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!targets.length) {
|
||||
return { template: genExprSource(templateExpr), patternExprs: [] };
|
||||
}
|
||||
|
||||
targets.sort((a, b) => getPatternNodeOrder(a.node) - getPatternNodeOrder(b.node));
|
||||
|
||||
const patternExprs = targets.map(({ patternExpr }) => cloneNode(patternExpr));
|
||||
|
||||
let currentExpr = templateExpr;
|
||||
targets.forEach(({ node }, index) => {
|
||||
currentExpr = replaceNode(node, placeholderAst(index), parents, currentExpr);
|
||||
});
|
||||
|
||||
const template = genExprSource(currentExpr);
|
||||
return { template, patternExprs };
|
||||
}
|
||||
|
||||
function getStrudelPatternExpr(node) {
|
||||
if (isStrudelPatternWrap(node)) {
|
||||
const arg = node.arguments?.[0];
|
||||
if (!arg) {
|
||||
throw new Error('S(...) requires an argument');
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
if (isMiniCall(node)) {
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isStrudelPatternWrap(node) {
|
||||
if (node.type !== 'CallExpression') {
|
||||
return false;
|
||||
}
|
||||
const callee = node.callee;
|
||||
if (callee.type === 'Identifier') {
|
||||
return callee.name === 'S';
|
||||
}
|
||||
if (callee.type === 'MemberExpression' && !callee.computed) {
|
||||
return callee.property?.name === 'S';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If `start` is available, we use it. If it's already been transpiled
|
||||
// to `m(...)`, use the provided offset
|
||||
function getPatternNodeOrder(node) {
|
||||
if (typeof node.start === 'number') {
|
||||
return node.start;
|
||||
}
|
||||
if (isMiniCall(node)) {
|
||||
const offsetArg = node.arguments?.[1];
|
||||
if (offsetArg?.type === 'Literal' && typeof offsetArg.value === 'number') {
|
||||
return offsetArg.value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function placeholderAst(index) {
|
||||
return {
|
||||
type: 'MemberExpression',
|
||||
object: { type: 'Identifier', name: 'pat' },
|
||||
property: { type: 'Literal', value: index },
|
||||
computed: true,
|
||||
optional: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Used to identify transpiled `m(...)` calls for proper conversion
|
||||
// to, say, kabelsalat placeholders
|
||||
function isMiniCall(node) {
|
||||
if (node.type !== 'CallExpression') {
|
||||
return false;
|
||||
}
|
||||
const callee = node.callee;
|
||||
if (callee.type !== 'Identifier') {
|
||||
return false;
|
||||
}
|
||||
if (callee.name !== getMinilangName()) {
|
||||
return false;
|
||||
}
|
||||
const firstArg = node.arguments?.[0];
|
||||
return firstArg?.type === 'Literal' && typeof firstArg.value === 'string';
|
||||
}
|
||||
|
||||
function getMinilangName() {
|
||||
const minilang = getLanguages().get('minilang');
|
||||
return minilang?.name || 'm';
|
||||
}
|
||||
|
||||
function replaceNode(node, replacement, parents, currentRoot) {
|
||||
const info = parents.get(node);
|
||||
if (!info || !info.parent) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
const { parent, prop, index } = info;
|
||||
if (Array.isArray(parent[prop])) {
|
||||
parent[prop][index] = replacement;
|
||||
} else {
|
||||
parent[prop] = replacement;
|
||||
}
|
||||
parents.set(replacement, { parent, prop, index });
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
function cloneNode(node) {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
}
|
||||
@@ -7,6 +7,11 @@ import { evaluate as _evaluate } from '@strudel/core';
|
||||
import { transpiler } from './transpiler.mjs';
|
||||
|
||||
export * from './transpiler.mjs';
|
||||
export * from './helpers.mjs';
|
||||
|
||||
import './plugin-kabelsalat.mjs';
|
||||
import './plugin-mini.mjs';
|
||||
import './plugin-sample.mjs';
|
||||
import './plugin-widgets.mjs';
|
||||
|
||||
export { registerWidgetType, getWidgetID } from './plugin-widgets.mjs';
|
||||
export const evaluate = (code, transpilerOptions) => _evaluate(code, transpiler, transpilerOptions);
|
||||
|
||||
@@ -3,9 +3,144 @@ plugin-kabelsalat.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/>.
|
||||
*/
|
||||
import { genExprSource, extractPatternPlaceholders } from './helpers.mjs';
|
||||
|
||||
export const transpilerPlugin = {
|
||||
import { walk } from 'estree-walker';
|
||||
import escodegen from 'escodegen';
|
||||
import { getLanguages, registerTranspilerPlugin } from './transpiler.mjs';
|
||||
|
||||
export function genExprSource(expr) {
|
||||
return escodegen.generate(expr, { format: { semicolons: false } });
|
||||
}
|
||||
|
||||
function isStrudelPatternWrap(node) {
|
||||
if (node.type !== 'CallExpression') {
|
||||
return false;
|
||||
}
|
||||
const callee = node.callee;
|
||||
if (callee.type === 'Identifier') {
|
||||
return callee.name === 'S';
|
||||
}
|
||||
if (callee.type === 'MemberExpression' && !callee.computed) {
|
||||
return callee.property?.name === 'S';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used to identify transpiled `m(...)` calls for proper conversion
|
||||
// to, say, kabelsalat placeholders
|
||||
function isMiniCall(node) {
|
||||
if (node.type !== 'CallExpression') {
|
||||
return false;
|
||||
}
|
||||
const callee = node.callee;
|
||||
if (callee.type !== 'Identifier') {
|
||||
return false;
|
||||
}
|
||||
if (callee.name !== getMinilangName()) {
|
||||
return false;
|
||||
}
|
||||
const firstArg = node.arguments?.[0];
|
||||
return firstArg?.type === 'Literal' && typeof firstArg.value === 'string';
|
||||
}
|
||||
|
||||
function getMinilangName() {
|
||||
const minilang = getLanguages().get('minilang');
|
||||
return minilang?.name || 'm';
|
||||
}
|
||||
|
||||
function replaceNode(node, replacement, parents, currentRoot) {
|
||||
const info = parents.get(node);
|
||||
if (!info || !info.parent) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
const { parent, prop, index } = info;
|
||||
if (Array.isArray(parent[prop])) {
|
||||
parent[prop][index] = replacement;
|
||||
} else {
|
||||
parent[prop] = replacement;
|
||||
}
|
||||
parents.set(replacement, { parent, prop, index });
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
// If `start` is available, we use it. If it's already been transpiled
|
||||
// to `m(...)`, use the provided offset
|
||||
function getPatternNodeOrder(node) {
|
||||
if (typeof node.start === 'number') {
|
||||
return node.start;
|
||||
}
|
||||
if (isMiniCall(node)) {
|
||||
const offsetArg = node.arguments?.[1];
|
||||
if (offsetArg?.type === 'Literal' && typeof offsetArg.value === 'number') {
|
||||
return offsetArg.value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function placeholderAst(index) {
|
||||
return {
|
||||
type: 'MemberExpression',
|
||||
object: { type: 'Identifier', name: 'pat' },
|
||||
property: { type: 'Literal', value: index },
|
||||
computed: true,
|
||||
optional: false,
|
||||
};
|
||||
}
|
||||
|
||||
function getStrudelPatternExpr(node) {
|
||||
if (isStrudelPatternWrap(node)) {
|
||||
const arg = node.arguments?.[0];
|
||||
if (!arg) {
|
||||
throw new Error('S(...) requires an argument');
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
if (isMiniCall(node)) {
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function cloneNode(node) {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
}
|
||||
|
||||
export function extractPatternPlaceholders(expr) {
|
||||
const templateExpr = cloneNode(expr);
|
||||
const parents = new Map();
|
||||
const targets = [];
|
||||
|
||||
walk(templateExpr, {
|
||||
enter(node, parent, prop, index) {
|
||||
parents.set(node, { parent, prop, index });
|
||||
const patternExpr = getStrudelPatternExpr(node);
|
||||
if (patternExpr) {
|
||||
targets.push({ node, patternExpr });
|
||||
this.skip();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!targets.length) {
|
||||
return { template: genExprSource(templateExpr), patternExprs: [] };
|
||||
}
|
||||
|
||||
targets.sort((a, b) => getPatternNodeOrder(a.node) - getPatternNodeOrder(b.node));
|
||||
|
||||
const patternExprs = targets.map(({ patternExpr }) => cloneNode(patternExpr));
|
||||
|
||||
let currentExpr = templateExpr;
|
||||
targets.forEach(({ node }, index) => {
|
||||
currentExpr = replaceNode(node, placeholderAst(index), parents, currentExpr);
|
||||
});
|
||||
|
||||
const template = genExprSource(currentExpr);
|
||||
return { template, patternExprs };
|
||||
}
|
||||
|
||||
const transpilerPlugin = {
|
||||
walk: (context) => ({
|
||||
leave: function (node, parent, prop, index) {
|
||||
if (!isKabelCall(node)) return;
|
||||
@@ -69,6 +204,8 @@ export const transpilerPlugin = {
|
||||
}),
|
||||
};
|
||||
|
||||
registerTranspilerPlugin(transpilerPlugin);
|
||||
|
||||
function isKabelCall(node) {
|
||||
if (node.type !== 'CallExpression') return false;
|
||||
let callee = node.callee;
|
||||
|
||||
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
||||
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/>.
|
||||
*/
|
||||
import { getLeafLocations } from '@strudel/mini';
|
||||
import { getLanguages } from './helpers.mjs';
|
||||
import { getLanguages, registerTranspilerPlugin } from './transpiler.mjs';
|
||||
|
||||
const languageLiteral = {
|
||||
walk: (context) => ({
|
||||
@@ -197,4 +197,4 @@ function isStringWithDoubleQuotes(node, locations, code) {
|
||||
return node.raw[0] === '"';
|
||||
}
|
||||
|
||||
export const miniTranspilerPlugins = [languageLiteral, tidal, backtick, doublequotes];
|
||||
registerTranspilerPlugin([languageLiteral, tidal, backtick, doublequotes]);
|
||||
|
||||
@@ -3,7 +3,15 @@ plugin-sample.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/>.
|
||||
*/
|
||||
import { withAwait } from './helpers.mjs';
|
||||
|
||||
import { registerTranspilerPlugin } from './transpiler.mjs';
|
||||
|
||||
export function withAwait(node) {
|
||||
return {
|
||||
type: 'AwaitExpression',
|
||||
argument: node,
|
||||
};
|
||||
}
|
||||
|
||||
const bareSample = {
|
||||
walk: (context) => ({
|
||||
@@ -18,4 +26,4 @@ function isBareSamplesCall(node, parent) {
|
||||
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
|
||||
}
|
||||
|
||||
export const sampleTranspilerPlugins = bareSample;
|
||||
registerTranspilerPlugin(bareSample);
|
||||
|
||||
@@ -3,6 +3,9 @@ plugin-widgets.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/>.
|
||||
*/
|
||||
|
||||
import { registerTranspilerPlugin } from './transpiler.mjs';
|
||||
|
||||
let widgetMethods = [];
|
||||
export function registerWidgetType(type) {
|
||||
widgetMethods.push(type);
|
||||
@@ -121,3 +124,5 @@ function widgetWithLocation(node, widgetConfig) {
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
registerTranspilerPlugin(widgetTranspilerPlugins);
|
||||
|
||||
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { transpiler } from '../transpiler.mjs';
|
||||
import { transpiler } from '../index.mjs';
|
||||
|
||||
const simple = { wrapAsync: false, addReturn: false, simpleLocs: true };
|
||||
|
||||
|
||||
@@ -3,15 +3,22 @@ transpiler.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/>.
|
||||
*/
|
||||
import { getLeafLocations } from '@strudel/mini';
|
||||
import { parse } from 'acorn';
|
||||
import escodegen from 'escodegen';
|
||||
import { walk } from 'estree-walker';
|
||||
import { getLanguages } from './helpers.mjs';
|
||||
import { miniTranspilerPlugins } from './plugin-mini.mjs';
|
||||
import { widgetTranspilerPlugins } from './plugin-widgets.mjs';
|
||||
import { sampleTranspilerPlugins } from './plugin-sample.mjs';
|
||||
import { transpilerPlugin as kabelsalatTranspilerPlugins } from './plugin-kabelsalat.mjs';
|
||||
|
||||
let languages = new Map();
|
||||
// config = { getLocations: (code: string, offset?: number) => number[][] }
|
||||
// see mondough.mjs for example use
|
||||
// the language will kick in when the code contains a template literal of type
|
||||
// example: mondo`...` will use language of type "mondo"
|
||||
// TODO: refactor tidal.mjs to use this
|
||||
export function registerLanguage(type, config) {
|
||||
languages.set(type, config);
|
||||
}
|
||||
export function getLanguages() {
|
||||
return languages;
|
||||
}
|
||||
|
||||
const plugins = [];
|
||||
|
||||
@@ -22,11 +29,6 @@ export function getPlugins() {
|
||||
return plugins.flat(Infinity);
|
||||
}
|
||||
|
||||
registerTranspilerPlugin(miniTranspilerPlugins);
|
||||
registerTranspilerPlugin(widgetTranspilerPlugins);
|
||||
registerTranspilerPlugin(sampleTranspilerPlugins);
|
||||
registerTranspilerPlugin(kabelsalatTranspilerPlugins);
|
||||
|
||||
export function transpiler(input, options = {}) {
|
||||
options = {
|
||||
wrapAsync: false,
|
||||
|
||||
Reference in New Issue
Block a user