mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 13:48:34 +00:00
move evaluate logic without transpiler to core
+ breaking change: evalScope is now imported from core + breaking change: deprecated extend is now removed + add repl.mjs
This commit is contained in:
parent
0485632e22
commit
371af755df
45
packages/core/evaluate.mjs
Normal file
45
packages/core/evaluate.mjs
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
evaluate.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.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 * as strudel from '@strudel.cycles/core';
|
||||
|
||||
const { isPattern, Pattern } = strudel;
|
||||
|
||||
let scoped = false;
|
||||
export const evalScope = async (...args) => {
|
||||
if (scoped) {
|
||||
console.warn('evalScope was called more than once.');
|
||||
}
|
||||
scoped = true;
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
results.forEach((result, i) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||
}
|
||||
});
|
||||
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
|
||||
};
|
||||
|
||||
function safeEval(str) {
|
||||
return Function('"use strict";return (' + str + ')')();
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler) => {
|
||||
if (!scoped) {
|
||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||
}
|
||||
if (transpiler) {
|
||||
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||
}
|
||||
let evaluated = await safeEval(code);
|
||||
if (!isPattern(evaluated)) {
|
||||
console.log('evaluated', evaluated);
|
||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||
}
|
||||
return { mode: 'javascript', pattern: evaluated };
|
||||
};
|
||||
@ -17,6 +17,7 @@ export * from './util.mjs';
|
||||
export * from './speak.mjs';
|
||||
export * from './clockworker.mjs';
|
||||
export * from './scheduler.mjs';
|
||||
export * from './evaluate.mjs';
|
||||
export { default as drawLine } from './drawLine.mjs';
|
||||
export { default as gist } from './gist.js';
|
||||
// below won't work with runtime.mjs (json import fails)
|
||||
|
||||
23
packages/core/repl.mjs
Normal file
23
packages/core/repl.mjs
Normal file
@ -0,0 +1,23 @@
|
||||
import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
|
||||
export function repl({ interval, defaultOutput, onSchedulerError, onEvalError, onEval, getTime, transpiler }) {
|
||||
const scheduler = new Cyclist({ interval, onTrigger: defaultOutput, onError: onSchedulerError, getTime });
|
||||
const evaluate = async (code) => {
|
||||
if (!code) {
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
const { pattern } = await _evaluate(code, transpiler);
|
||||
scheduler.setPattern(pattern);
|
||||
onEval({
|
||||
pattern,
|
||||
code,
|
||||
});
|
||||
} catch (err) {
|
||||
onEvalError?.(err);
|
||||
console.warn('eval error', err);
|
||||
}
|
||||
};
|
||||
return { scheduler, evaluate };
|
||||
}
|
||||
@ -11,10 +11,9 @@ npm i @strudel.cycles/eval --save
|
||||
|
||||
## Example
|
||||
|
||||
<!-- TODO: -extend +evalScope -->
|
||||
|
||||
```js
|
||||
import { evaluate, extend } from '@strudel.cycles/eval';
|
||||
import { evalScope } from '@strudel.cycles/core';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
|
||||
evalScope(
|
||||
import('@strudel.cycles/core'),
|
||||
|
||||
@ -4,46 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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 { evaluate as _evaluate } from '@strudel.cycles/core';
|
||||
import shapeshifter from './shapeshifter.mjs';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
|
||||
const { isPattern, Pattern } = strudel;
|
||||
|
||||
export const extend = (...args) => {
|
||||
console.warn('@strudel.cycles/eval extend is deprecated, please use evalScope instead');
|
||||
Object.assign(globalThis, ...args);
|
||||
};
|
||||
|
||||
let scoped = false;
|
||||
export const evalScope = async (...args) => {
|
||||
if (scoped) {
|
||||
console.warn('@strudel.cycles/eval evalScope was called more than once.');
|
||||
}
|
||||
scoped = true;
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
results.forEach((result, i) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||
}
|
||||
});
|
||||
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
|
||||
};
|
||||
|
||||
function safeEval(str) {
|
||||
return Function('"use strict";return (' + str + ')')();
|
||||
}
|
||||
|
||||
export const evaluate = async (code) => {
|
||||
if (!scoped) {
|
||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||
}
|
||||
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
|
||||
let evaluated = await safeEval(shapeshifted);
|
||||
if (!isPattern(evaluated)) {
|
||||
console.log('evaluated', evaluated);
|
||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||
}
|
||||
return { mode: 'javascript', pattern: evaluated };
|
||||
return _evaluate(code, shapeshifter);
|
||||
};
|
||||
|
||||
@ -6,10 +6,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { expect, describe, it } from 'vitest';
|
||||
|
||||
import { evaluate, evalScope } from '../evaluate.mjs';
|
||||
import { evaluate } from '../evaluate.mjs';
|
||||
import { mini } from '@strudel.cycles/mini';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
const { fastcat } = strudel;
|
||||
const { fastcat, evalScope } = strudel;
|
||||
|
||||
describe('evaluate', async () => {
|
||||
await evalScope({ mini }, strudel);
|
||||
|
||||
@ -13,9 +13,8 @@ npm i @strudel.cycles/react
|
||||
Here is a minimal example of how to set up a MiniRepl:
|
||||
|
||||
```jsx
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { MiniRepl } from '@strudel.cycles/react';
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
import { prebake } from '../repl/src/prebake.mjs';
|
||||
|
||||
evalScope(
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { useCallback, useState } from 'react';
|
||||
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
|
||||
@ -94,7 +93,7 @@ function App() {
|
||||
if (e.code === 'Enter') {
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await evaluate();
|
||||
await evaluate(code);
|
||||
if (e.shiftKey) {
|
||||
panic();
|
||||
scheduler.stop();
|
||||
@ -120,7 +119,7 @@ function App() {
|
||||
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
|
||||
<button
|
||||
onClick={async () => {
|
||||
await evaluate();
|
||||
await evaluate(code);
|
||||
await getAudioContext().resume();
|
||||
scheduler.start();
|
||||
}}
|
||||
@ -128,7 +127,7 @@ function App() {
|
||||
start
|
||||
</button>
|
||||
<button onClick={() => scheduler.stop()}>stop</button>
|
||||
{isDirty && <button onClick={() => evaluate()}>eval</button>}
|
||||
{isDirty && <button onClick={() => evaluate(code)}>eval</button>}
|
||||
</div>
|
||||
{error && <p>error {error.message}</p>}
|
||||
</nav>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { MiniRepl } from './components/MiniRepl';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { controls } from '@strudel.cycles/core';
|
||||
import { controls, evalScope } from '@strudel.cycles/core';
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// import { Scheduler } from '@strudel.cycles/core';
|
||||
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { evaluate as _evaluate } from '@strudel.cycles/eval';
|
||||
import { Cyclist } from '@strudel.cycles/core/cyclist.mjs';
|
||||
import shapeshifter from '@strudel.cycles/eval/shapeshifter.mjs';
|
||||
import { repl } from '@strudel.cycles/core/repl.mjs';
|
||||
|
||||
function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = false }) {
|
||||
// scheduler
|
||||
@ -10,37 +10,33 @@ function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = fals
|
||||
const [activeCode, setActiveCode] = useState(code);
|
||||
const [pattern, setPattern] = useState();
|
||||
const isDirty = code !== activeCode;
|
||||
// TODO: how / when to remove schedulerError?
|
||||
const scheduler = useMemo(
|
||||
// () => new Scheduler({ interval, onTrigger: defaultOutput, onError: setSchedulerError, getTime }),
|
||||
() => new Cyclist({ interval, onTrigger: defaultOutput, onError: setSchedulerError, getTime }),
|
||||
[defaultOutput, interval],
|
||||
const { scheduler, evaluate: _evaluate } = useMemo(
|
||||
() =>
|
||||
repl({
|
||||
interval,
|
||||
defaultOutput,
|
||||
onSchedulerError: setSchedulerError,
|
||||
onEvalError: setEvalError,
|
||||
getTime,
|
||||
transpiler: shapeshifter,
|
||||
onEval: ({ pattern: _pattern, code }) => {
|
||||
setActiveCode(code);
|
||||
setPattern(_pattern);
|
||||
setEvalError();
|
||||
},
|
||||
onEvalError: setEvalError,
|
||||
}),
|
||||
[defaultOutput, interval, getTime],
|
||||
);
|
||||
const evaluate = useCallback(async () => {
|
||||
if (!code) {
|
||||
console.log('no code..');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// TODO: let user inject custom eval function?
|
||||
const { pattern: _pattern } = await _evaluate(code);
|
||||
setActiveCode(code);
|
||||
scheduler?.setPattern(_pattern);
|
||||
setPattern(_pattern);
|
||||
setEvalError();
|
||||
} catch (err) {
|
||||
setEvalError(err);
|
||||
console.warn('eval error', err);
|
||||
}
|
||||
}, [code, scheduler]);
|
||||
const evaluate = useCallback(() => _evaluate(code), [_evaluate, code]);
|
||||
|
||||
const inited = useRef();
|
||||
useEffect(() => {
|
||||
if (!inited.current && evalOnMount) {
|
||||
if (!inited.current && evalOnMount && code) {
|
||||
inited.current = true;
|
||||
evaluate();
|
||||
}
|
||||
}, [evaluate, evalOnMount]);
|
||||
}, [evaluate, evalOnMount, code]);
|
||||
|
||||
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern };
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"name": "@strudel.cycles/repl",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"start": "vite",
|
||||
|
||||
@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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 { evalScope, evaluate } from '@strudel.cycles/eval';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { CodeMirror, cx, flash, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react';
|
||||
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
@ -14,7 +14,7 @@ import * as tunes from './tunes.mjs';
|
||||
import { prebake } from './prebake.mjs';
|
||||
import * as WebDirt from 'WebDirt';
|
||||
import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { controls } from '@strudel.cycles/core';
|
||||
import { controls, evalScope } from '@strudel.cycles/core';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
// import * as tunes from './tunes.mjs';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { evalScope } from '@strudel.cycles/core';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
import * as webaudio from '@strudel.cycles/webaudio';
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
import { samples } from '@strudel.cycles/webaudio';
|
||||
import { prebake } from '../repl/src/prebake.mjs';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user