mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-26 04:58:27 +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 './speak.mjs';
|
||||||
export * from './clockworker.mjs';
|
export * from './clockworker.mjs';
|
||||||
export * from './scheduler.mjs';
|
export * from './scheduler.mjs';
|
||||||
|
export * from './evaluate.mjs';
|
||||||
export { default as drawLine } from './drawLine.mjs';
|
export { default as drawLine } from './drawLine.mjs';
|
||||||
export { default as gist } from './gist.js';
|
export { default as gist } from './gist.js';
|
||||||
// below won't work with runtime.mjs (json import fails)
|
// 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
|
## Example
|
||||||
|
|
||||||
<!-- TODO: -extend +evalScope -->
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { evaluate, extend } from '@strudel.cycles/eval';
|
import { evalScope } from '@strudel.cycles/core';
|
||||||
|
import { evaluate } from '@strudel.cycles/eval';
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
import('@strudel.cycles/core'),
|
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/>.
|
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 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) => {
|
export const evaluate = async (code) => {
|
||||||
if (!scoped) {
|
return _evaluate(code, shapeshifter);
|
||||||
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 };
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 { expect, describe, it } from 'vitest';
|
||||||
|
|
||||||
import { evaluate, evalScope } from '../evaluate.mjs';
|
import { evaluate } from '../evaluate.mjs';
|
||||||
import { mini } from '@strudel.cycles/mini';
|
import { mini } from '@strudel.cycles/mini';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
const { fastcat } = strudel;
|
const { fastcat, evalScope } = strudel;
|
||||||
|
|
||||||
describe('evaluate', async () => {
|
describe('evaluate', async () => {
|
||||||
await evalScope({ mini }, strudel);
|
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:
|
Here is a minimal example of how to set up a MiniRepl:
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import { evalScope } from '@strudel.cycles/eval';
|
import { evalScope, controls } from '@strudel.cycles/core';
|
||||||
import { MiniRepl } from '@strudel.cycles/react';
|
import { MiniRepl } from '@strudel.cycles/react';
|
||||||
import controls from '@strudel.cycles/core/controls.mjs';
|
|
||||||
import { prebake } from '../repl/src/prebake.mjs';
|
import { prebake } from '../repl/src/prebake.mjs';
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import controls from '@strudel.cycles/core/controls.mjs';
|
import { evalScope, controls } from '@strudel.cycles/core';
|
||||||
import { evalScope } from '@strudel.cycles/eval';
|
|
||||||
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
|
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
|
||||||
@ -94,7 +93,7 @@ function App() {
|
|||||||
if (e.code === 'Enter') {
|
if (e.code === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
flash(view);
|
flash(view);
|
||||||
await evaluate();
|
await evaluate(code);
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
panic();
|
panic();
|
||||||
scheduler.stop();
|
scheduler.stop();
|
||||||
@ -120,7 +119,7 @@ function App() {
|
|||||||
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
|
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await evaluate();
|
await evaluate(code);
|
||||||
await getAudioContext().resume();
|
await getAudioContext().resume();
|
||||||
scheduler.start();
|
scheduler.start();
|
||||||
}}
|
}}
|
||||||
@ -128,7 +127,7 @@ function App() {
|
|||||||
start
|
start
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => scheduler.stop()}>stop</button>
|
<button onClick={() => scheduler.stop()}>stop</button>
|
||||||
{isDirty && <button onClick={() => evaluate()}>eval</button>}
|
{isDirty && <button onClick={() => evaluate(code)}>eval</button>}
|
||||||
</div>
|
</div>
|
||||||
{error && <p>error {error.message}</p>}
|
{error && <p>error {error.message}</p>}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MiniRepl } from './components/MiniRepl';
|
import { MiniRepl } from './components/MiniRepl';
|
||||||
import 'tailwindcss/tailwind.css';
|
import 'tailwindcss/tailwind.css';
|
||||||
import { evalScope } from '@strudel.cycles/eval';
|
import { controls, evalScope } from '@strudel.cycles/core';
|
||||||
import { controls } from '@strudel.cycles/core';
|
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
controls,
|
controls,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// import { Scheduler } from '@strudel.cycles/core';
|
// import { Scheduler } from '@strudel.cycles/core';
|
||||||
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { evaluate as _evaluate } from '@strudel.cycles/eval';
|
import shapeshifter from '@strudel.cycles/eval/shapeshifter.mjs';
|
||||||
import { Cyclist } from '@strudel.cycles/core/cyclist.mjs';
|
import { repl } from '@strudel.cycles/core/repl.mjs';
|
||||||
|
|
||||||
function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = false }) {
|
function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = false }) {
|
||||||
// scheduler
|
// scheduler
|
||||||
@ -10,37 +10,33 @@ function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = fals
|
|||||||
const [activeCode, setActiveCode] = useState(code);
|
const [activeCode, setActiveCode] = useState(code);
|
||||||
const [pattern, setPattern] = useState();
|
const [pattern, setPattern] = useState();
|
||||||
const isDirty = code !== activeCode;
|
const isDirty = code !== activeCode;
|
||||||
// TODO: how / when to remove schedulerError?
|
const { scheduler, evaluate: _evaluate } = useMemo(
|
||||||
const scheduler = useMemo(
|
() =>
|
||||||
// () => new Scheduler({ interval, onTrigger: defaultOutput, onError: setSchedulerError, getTime }),
|
repl({
|
||||||
() => new Cyclist({ interval, onTrigger: defaultOutput, onError: setSchedulerError, getTime }),
|
interval,
|
||||||
[defaultOutput, 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 () => {
|
const evaluate = useCallback(() => _evaluate(code), [_evaluate, code]);
|
||||||
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 inited = useRef();
|
const inited = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!inited.current && evalOnMount) {
|
if (!inited.current && evalOnMount && code) {
|
||||||
inited.current = true;
|
inited.current = true;
|
||||||
evaluate();
|
evaluate();
|
||||||
}
|
}
|
||||||
}, [evaluate, evalOnMount]);
|
}, [evaluate, evalOnMount, code]);
|
||||||
|
|
||||||
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern };
|
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
"name": "@strudel.cycles/repl",
|
"name": "@strudel.cycles/repl",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"start": "vite",
|
"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/>.
|
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 { CodeMirror, cx, flash, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react';
|
||||||
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
||||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
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 { prebake } from './prebake.mjs';
|
||||||
import * as WebDirt from 'WebDirt';
|
import * as WebDirt from 'WebDirt';
|
||||||
import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
|
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 { createClient } from '@supabase/supabase-js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// import * as tunes from './tunes.mjs';
|
// import * as tunes from './tunes.mjs';
|
||||||
import { evaluate } from '@strudel.cycles/eval';
|
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 strudel from '@strudel.cycles/core';
|
||||||
import * as webaudio from '@strudel.cycles/webaudio';
|
import * as webaudio from '@strudel.cycles/webaudio';
|
||||||
import controls from '@strudel.cycles/core/controls.mjs';
|
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 { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
|
||||||
import controls from '@strudel.cycles/core/controls.mjs';
|
|
||||||
import { samples } from '@strudel.cycles/webaudio';
|
import { samples } from '@strudel.cycles/webaudio';
|
||||||
import { prebake } from '../repl/src/prebake.mjs';
|
import { prebake } from '../repl/src/prebake.mjs';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user