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:
Felix Roos 2022-11-06 12:12:43 +01:00
parent 0485632e22
commit 371af755df
14 changed files with 107 additions and 85 deletions

View 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 };
};

View File

@ -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
View 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 };
}

View File

@ -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'),

View File

@ -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);
};

View File

@ -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);

View File

@ -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(

View File

@ -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>

View File

@ -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,

View File

@ -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 };
}

View File

@ -2,7 +2,6 @@
"name": "@strudel.cycles/repl",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"start": "vite",

View File

@ -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';

View File

@ -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';

View File

@ -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';