diff --git a/compiler/package.json b/compiler/package.json index f561eb1..7090a73 100644 --- a/compiler/package.json +++ b/compiler/package.json @@ -21,7 +21,7 @@ "dist:esm": "tsc -p tsconfig.esm.json", "dist:cjs": "tsc -p tsconfig.cjs.json", "dist": "npm run parseTypeDocs && rm -rf ./dist/* && npm run dist:esm && npm run dist:cjs && node ./scripts/setVersionConstant.mjs", - "test": "c8 mocha './dist/cjs/**/*.spec.js'", + "test": "c8 mocha './dist/cjs/**/*.spec.js' --reporter-option maxDiffSize=0", "test:single": "mocha './dist/cjs/**/*.spec.js' --reporter-option maxDiffSize=0 --grep", "check": "eslint ./src && tsc --noEmit", "parseTypeDocs": "node ./scripts/parseTypeDocs.mjs" diff --git a/compiler/src/js2eel/compiler/Js2EelCompiler.spec.ts b/compiler/src/js2eel/compiler/Js2EelCompiler.spec.ts index 809dc0a..c35800a 100644 --- a/compiler/src/js2eel/compiler/Js2EelCompiler.spec.ts +++ b/compiler/src/js2eel/compiler/Js2EelCompiler.spec.ts @@ -278,10 +278,14 @@ someVar = 1; /* Channel 0 */ +CH__0 = 0; + someVar += ; /* Channel 1 */ +CH__1 = 1; + someVar += ; @@ -326,10 +330,14 @@ out_pin:In 1 /* Channel 0 */ +CH__0 = 0; + someVar__S4 = 1; /* Channel 1 */ +CH__1 = 1; + someVar__S5 = 1; diff --git a/compiler/src/js2eel/generatorNodes/assignmentExpression/assignmentExpression.spec.ts b/compiler/src/js2eel/generatorNodes/assignmentExpression/assignmentExpression.spec.ts index c353d41..aca9ff0 100644 --- a/compiler/src/js2eel/generatorNodes/assignmentExpression/assignmentExpression.spec.ts +++ b/compiler/src/js2eel/generatorNodes/assignmentExpression/assignmentExpression.spec.ts @@ -435,11 +435,15 @@ out_pin:In 1 /* Channel 0 */ +CH__0 = 0; + spl0 = 4; = 3; /* Channel 1 */ +CH__1 = 1; + spl1 = 4; = 3; diff --git a/compiler/src/js2eel/generatorNodes/callExpression/callExpression.ts b/compiler/src/js2eel/generatorNodes/callExpression/callExpression.ts index a458414..6b85f48 100644 --- a/compiler/src/js2eel/generatorNodes/callExpression/callExpression.ts +++ b/compiler/src/js2eel/generatorNodes/callExpression/callExpression.ts @@ -11,6 +11,7 @@ import { memberExpressionCall } from '../memberExpression/memberExpressionCall.j import { evaluateUserFunctionCall } from './utils/evaluateUserFunctionCall.js'; import { suffixInlineReturn } from '../../suffixersAndPrefixers/suffixInlineReturn.js'; import { prefixParam } from '../../suffixersAndPrefixers/prefixParam.js'; +import { stripChannelPrefix } from '../../suffixersAndPrefixers/prefixChannel.js'; import { addSemicolonIfNone } from '../../suffixersAndPrefixers/addSemicolonIfNone.js'; import { EEL_LIBRARY_FUNCTION_NAMES } from '../../constants.js'; @@ -99,16 +100,22 @@ export const callExpression = ( let replacedReturnSource = returnSrc?.src; for (const [_key, arg] of Object.entries(args)) { + let cleanValue = arg.value; + + if (typeof cleanValue === 'string') { + cleanValue = stripChannelPrefix(arg.value); + } + // FIXME this might hurt performance if functions get large replacedBodySrc = replacedBodySrc.replaceAll( `${prefixParam(arg.scopedName)}`, - arg.value + cleanValue ); if (replacedReturnSource) { replacedReturnSource = replacedReturnSource.replaceAll( `${prefixParam(arg.scopedName)}`, - arg.value + cleanValue ); } } diff --git a/compiler/src/js2eel/generatorNodes/callExpression/eelLib/eelLibraryFunctionCall.spec.ts b/compiler/src/js2eel/generatorNodes/callExpression/eelLib/eelLibraryFunctionCall.spec.ts index 4f7ddb0..0140356 100644 --- a/compiler/src/js2eel/generatorNodes/callExpression/eelLib/eelLibraryFunctionCall.spec.ts +++ b/compiler/src/js2eel/generatorNodes/callExpression/eelLib/eelLibraryFunctionCall.spec.ts @@ -98,11 +98,15 @@ out_pin:In 1 /* Channel 0 */ -myVar__S4 = spl(0); +CH__0 = 0; + +myVar__S4 = spl(CH__0); /* Channel 1 */ -myVar__S5 = spl(1); +CH__1 = 1; + +myVar__S5 = spl(CH__1); diff --git a/compiler/src/js2eel/generatorNodes/callExpression/js2EelLib/eachChannel.spec.ts b/compiler/src/js2eel/generatorNodes/callExpression/js2EelLib/eachChannel.spec.ts index 4a02422..410fb76 100644 --- a/compiler/src/js2eel/generatorNodes/callExpression/js2EelLib/eachChannel.spec.ts +++ b/compiler/src/js2eel/generatorNodes/callExpression/js2EelLib/eachChannel.spec.ts @@ -116,9 +116,13 @@ out_pin:In 1 /* Channel 0 */ +CH__0 = 0; + /* Channel 1 */ +CH__1 = 1; + diff --git a/compiler/src/js2eel/generatorNodes/callExpression/utils/evaluateUserFunctionCall.ts b/compiler/src/js2eel/generatorNodes/callExpression/utils/evaluateUserFunctionCall.ts index 9d41afb..2728b8f 100644 --- a/compiler/src/js2eel/generatorNodes/callExpression/utils/evaluateUserFunctionCall.ts +++ b/compiler/src/js2eel/generatorNodes/callExpression/utils/evaluateUserFunctionCall.ts @@ -117,7 +117,7 @@ export const evaluateUserFunctionCall = ( break; } case 'Identifier': { - const id = identifier(givenArg, instance); + const id = identifier(givenArg, instance, { isParam: true }); value = id; rawValue = id; break; diff --git a/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.spec.ts b/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.spec.ts index 16c1ac5..420fcb6 100644 --- a/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.spec.ts +++ b/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.spec.ts @@ -257,6 +257,87 @@ out_pin:In 1 spl3 = spl0 == 1 ? 3 : -4 * 2; +`) + ); + expect(result.errors.length).to.equal(0); + }); + + it('Consequent is member expression', () => { + const compiler = new Js2EelCompiler(); + const result = + compiler.compile(`config({ description: "conditional", inChannels: 2, outChannels: 2 }); + +const myEelbuf = new EelBuffer(2, 2); + +onSample(() => { + spl3 = spl0 === 1 ? myEelbuf[0][1] : -4 * 2; +});`); + + expect(result.success).to.equal(true); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.9.1 */ + +desc:conditional + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +@init + +myEelbuf__B0 = 0 * 2; +myEelbuf__B1 = 1 * 2; +myEelbuf__size = 2; + + +@sample + +spl3 = spl0 == 1 ? myEelbuf__B0[1] : -4 * 2; + + +`) + ); + expect(result.errors.length).to.equal(0); + }); + + it('Alternate is member expression', () => { + const compiler = new Js2EelCompiler(); + const result = + compiler.compile(`config({ description: 'conditional', inChannels: 2, outChannels: 2 }); + +const myEelbuf = new EelBuffer(2, 2); + +onSample(() => { + spl3 = spl0 === 1 ? 3 : myEelbuf[1][1]; +}); +`); + + expect(result.success).to.equal(true); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.9.1 */ + +desc:conditional + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +@init + +myEelbuf__B0 = 0 * 2; +myEelbuf__B1 = 1 * 2; +myEelbuf__size = 2; + + +@sample + +spl3 = spl0 == 1 ? 3 : myEelbuf__B1[1]; + + `) ); expect(result.errors.length).to.equal(0); diff --git a/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.ts b/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.ts index 0e98690..47bfca8 100644 --- a/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.ts +++ b/compiler/src/js2eel/generatorNodes/conditionalExpression/conditionalExpression.ts @@ -2,6 +2,7 @@ import { literal } from '../literal/literal'; import { identifier } from '../identifier/identifier'; import { unaryExpression } from '../unaryExpression/unaryExpression'; import { binaryExpression } from '../binaryExpression/binaryExpression'; +import { memberExpression } from '../memberExpression/memberExpression'; import type { ConditionalExpression } from 'estree'; import type { Js2EelCompiler } from '../../compiler/Js2EelCompiler'; @@ -51,10 +52,18 @@ export const conditionalExpression = ( ); break; } + case 'MemberExpression': { + conditionalExpressionSrc += memberExpression( + conditionalExpressionNode, + conditionalExpressionNode.consequent, + instance + ); + break; + } default: { instance.error( 'TypeError', - `Type ${conditionalExpressionNode.consequent.type} not allowed`, + `Conditional expression: Consequent: Type ${conditionalExpressionNode.consequent.type} not allowed`, conditionalExpressionNode.consequent ); } @@ -88,10 +97,18 @@ export const conditionalExpression = ( ); break; } + case 'MemberExpression': { + conditionalExpressionSrc += memberExpression( + conditionalExpressionNode, + conditionalExpressionNode.alternate, + instance + ); + break; + } default: { instance.error( 'TypeError', - `Type ${conditionalExpressionNode.alternate.type} not allowed`, + `Conditional expression: Alternate: Type ${conditionalExpressionNode.alternate.type} not allowed`, conditionalExpressionNode.alternate ); } diff --git a/compiler/src/js2eel/generatorNodes/functionExpression/functionExpression.ts b/compiler/src/js2eel/generatorNodes/functionExpression/functionExpression.ts index 5bf2354..d5ce83e 100644 --- a/compiler/src/js2eel/generatorNodes/functionExpression/functionExpression.ts +++ b/compiler/src/js2eel/generatorNodes/functionExpression/functionExpression.ts @@ -1,6 +1,7 @@ import { blockStatement } from '../blockStatement/blockStatement.js'; import { registerDeclarationParam } from '../../declarationParams/registerDeclarationParam.js'; +import { prefixChannel } from '../../suffixersAndPrefixers/prefixChannel.js'; import type { ArrowFunctionExpression, FunctionExpression } from 'estree'; import type { Js2EelCompiler } from '../../compiler/Js2EelCompiler.js'; @@ -103,7 +104,11 @@ export const functionExpression = ( i < instance.getChannels().inChannels; /* FIXME is this sufficient? what about out? */ i++ ) { - callbackSrc += `/* Channel ${i} */\n\n`; + callbackSrc += `/* Channel ${i} */ + +${prefixChannel(i)} = ${i}; + +`; instance.setCurrentChannel(i); @@ -190,7 +195,7 @@ export const functionExpression = ( default: { instance.error( 'GenericError', - "Function expressions are only allowed as arguments to onInit(), onBlock(), onSample(), eachChannel() and onSlider()", + 'Function expressions are only allowed as arguments to onInit(), onBlock(), onSample(), eachChannel() and onSlider()', functionExpression ); } diff --git a/compiler/src/js2eel/generatorNodes/identifier/identifier.spec.ts b/compiler/src/js2eel/generatorNodes/identifier/identifier.spec.ts new file mode 100644 index 0000000..a000ef0 --- /dev/null +++ b/compiler/src/js2eel/generatorNodes/identifier/identifier.spec.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import { Js2EelCompiler } from '../../compiler/Js2EelCompiler'; +import { testEelSrc } from '../../test/helpers'; + +describe('identifier()', () => { + it('error if using EelArray or EelBuffer without accessors', () => { + const compiler = new Js2EelCompiler(); + const result = + compiler.compile(`config({ description: 'mono_delay', inChannels: 2, outChannels: 2 }); + +const myEelArray = new EelArray(2, 2); +const myVar = myEelArray;`); + + expect(result.success).to.equal(false); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.9.1 */ + +desc:mono_delay + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +@init + +myVar = ?ä__DENY_COMPILATION; + + +`) + ); + expect(result.errors.length).to.equal(1); + expect(result.errors[0].type).to.equal('GenericError'); + }); + +}); diff --git a/compiler/src/js2eel/generatorNodes/identifier/identifier.ts b/compiler/src/js2eel/generatorNodes/identifier/identifier.ts index ccce01f..1c29fe0 100644 --- a/compiler/src/js2eel/generatorNodes/identifier/identifier.ts +++ b/compiler/src/js2eel/generatorNodes/identifier/identifier.ts @@ -5,12 +5,17 @@ import { suffixScopeBySymbol } from '../../suffixersAndPrefixers/suffixScope.js'; import { prefixParam } from '../../suffixersAndPrefixers/prefixParam.js'; -import { EEL_LIBRARY_VARS } from '../../constants.js'; +import { prefixChannel } from '../../suffixersAndPrefixers/prefixChannel.js'; +import { EEL_LIBRARY_VARS, JSFX_DENY_COMPILATION } from '../../constants.js'; import type { Identifier } from 'estree'; import type { Js2EelCompiler } from '../../compiler/Js2EelCompiler.js'; -export const identifier = (identifier: Identifier, instance: Js2EelCompiler): string => { +export const identifier = ( + identifier: Identifier, + instance: Js2EelCompiler, + additionalData?: { isObjectInMemberExpression?: boolean; isParam?: boolean } +): string => { if (EEL_LIBRARY_VARS.has(identifier.name.toLowerCase())) { // We can lowercase here because there's only lowercase symbols in EEL return identifier.name; @@ -20,6 +25,21 @@ export const identifier = (identifier: Identifier, instance: Js2EelCompiler): st const declaredSymbol = getSymbolInNextUpScope(identifier.name, instance); + if ( + (declaredSymbol?.symbol.currentAssignment?.type === 'EelArray' || + declaredSymbol?.symbol.currentAssignment?.type === 'EelBuffer') && + (!additionalData?.isObjectInMemberExpression && + !additionalData?.isParam) + ) { + instance.error( + 'GenericError', + 'EelBuffer/EelArray cannot be used without accessors: ' + identifier.name, + identifier + ); + + return JSFX_DENY_COMPILATION; + } + const sampleParamsMap = instance.getEachChannelParams(); const inSampleParamsMap = Object.values(sampleParamsMap).includes(identifier.name); @@ -39,7 +59,7 @@ export const identifier = (identifier: Identifier, instance: Js2EelCompiler): st const currentChannel = instance.getCurrentChannel(); if (sampleParamsMap.channelIdentifier === identifier.name) { - identifierSrc = currentChannel.toString(); + identifierSrc = `${prefixChannel(currentChannel)}`; } else { identifierSrc = `spl${currentChannel}`; } diff --git a/compiler/src/js2eel/generatorNodes/memberExpression/js2EelLib/eelArrayMemberCall.ts b/compiler/src/js2eel/generatorNodes/memberExpression/js2EelLib/eelArrayMemberCall.ts index 420f988..c8c97c8 100644 --- a/compiler/src/js2eel/generatorNodes/memberExpression/js2EelLib/eelArrayMemberCall.ts +++ b/compiler/src/js2eel/generatorNodes/memberExpression/js2EelLib/eelArrayMemberCall.ts @@ -4,7 +4,6 @@ import type { EelArray } from '../../../types.js'; export const eelArrayMemberCall = ( eelArray: EelArray, - calleeObject: Identifier, calleeProperty: Identifier | PrivateIdentifier, instance: Js2EelCompiler ): string => { diff --git a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionCall.ts b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionCall.ts index b3b7600..44b7eae 100644 --- a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionCall.ts +++ b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionCall.ts @@ -79,7 +79,6 @@ export const memberExpressionCall = ( } else if (eelArray) { parentCallExpressionSrc += eelArrayMemberCall( eelArray, - calleeObject, calleeProperty, instance ); diff --git a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.spec.ts b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.spec.ts index 5b34d13..f5f081f 100644 --- a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.spec.ts +++ b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.spec.ts @@ -101,7 +101,6 @@ myVar__S2 = myArr__D?ä__DENY_COMPILATION__2; expect(result.errors[0].type).to.equal('TypeError'); }); - it('error if property access performed on wrong type: with just one dimension', () => { const compiler = new Js2EelCompiler(); @@ -369,6 +368,55 @@ buf__size = 2; myVar2__S2 = buf__B1[1]; +`) + ); + expect(result.errors.length).to.equal(0); + }); + + it('Array access with eachChannel() ch param as position accessor', () => { + const compiler = new Js2EelCompiler(); + + const result = + compiler.compile(`config({ description: 'member_expression_computed', inChannels: 2, outChannels: 2 }); + +const buf = new EelArray(2, 2); + +onSample(() => { + eachChannel((_sample, channel) => { + const myConst = buf[0][channel]; + }); +}); +`); + + expect(result.success).to.equal(true); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.9.1 */ + +desc:member_expression_computed + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +@sample + + +/* Channel 0 */ + +CH__0 = 0; + +myConst__S4 = buf__D0__0; + +/* Channel 1 */ + +CH__1 = 1; + +myConst__S5 = buf__D0__1; + + + `) ); expect(result.errors.length).to.equal(0); diff --git a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.ts b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.ts index d06dfec..eddfd99 100644 --- a/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.ts +++ b/compiler/src/js2eel/generatorNodes/memberExpression/memberExpressionComputed.ts @@ -7,6 +7,8 @@ import { suffixEelBuffer } from '../../suffixersAndPrefixers/suffixEelBuffer.js' import { suffixEelArray } from '../../suffixersAndPrefixers/suffixEelArray.js'; import { prefixParam } from '../../suffixersAndPrefixers/prefixParam.js'; import { suffixScopeByScopeSuffix } from '../../suffixersAndPrefixers/suffixScope.js'; +import { callExpression } from '../callExpression/callExpression.js'; +import { stripChannelPrefix } from '../../suffixersAndPrefixers/prefixChannel.js'; import { JSFX_DENY_COMPILATION } from '../../constants.js'; import type { Js2EelCompiler } from '../../compiler/Js2EelCompiler.js'; @@ -50,7 +52,9 @@ export const memberExpressionComputed = ( potentialArray = instance.getEelArray(potentialArrayOrBufferName); // Will mark the symbol as used and give error if doesn't exist. We don't use the return string - identifier(dimensionPart.object, instance); + identifier(dimensionPart.object, instance, { + isObjectInMemberExpression: true + }); } } else { instance.error( @@ -230,6 +234,9 @@ export const memberExpressionComputed = ( } } + dimensionText = stripChannelPrefix(dimensionText); + positionText = stripChannelPrefix(positionText); + if (isParam && declaredSymbol) { // we suffix the param name to catch it in the callExpression() replacements... computedMemberExpressionSrc += prefixParam( diff --git a/compiler/src/js2eel/generatorNodes/newExpression/newExpression.ts b/compiler/src/js2eel/generatorNodes/newExpression/newExpression.ts index 2494a3c..b2c0c75 100644 --- a/compiler/src/js2eel/generatorNodes/newExpression/newExpression.ts +++ b/compiler/src/js2eel/generatorNodes/newExpression/newExpression.ts @@ -1,17 +1,21 @@ import { newEelBuffer } from './js2EelLib/newEelBuffer.js'; import { newEelArray } from './js2EelLib/newEelArray.js'; +import { JSFX_DENY_COMPILATION } from '../../constants.js'; import type { NewExpression } from 'estree'; import type { Js2EelCompiler } from '../../compiler/Js2EelCompiler.js'; +import type { EelNewExpressionType } from '../../types.js'; export const newExpression = ( newExpression: NewExpression, symbolName: string, instance: Js2EelCompiler -): string => { +): { newType: EelNewExpressionType | null; src: string } => { + let newType: EelNewExpressionType | null = null; + if (instance.getCurrentScopePath() !== 'root') { instance.error('ScopeError', 'Class instantiation not allowed here.', newExpression); - return ''; + return { newType, src: JSFX_DENY_COMPILATION }; } const newExpressionSrc = ''; @@ -24,7 +28,7 @@ export const newExpression = ( newExpression.callee ); - return ''; + return { newType, src: JSFX_DENY_COMPILATION }; } /* c8 ignore stop */ @@ -32,12 +36,16 @@ export const newExpression = ( case 'EelBuffer': { newEelBuffer(newExpression, symbolName, instance); + newType = 'EelBuffer'; + break; } case 'EelArray': { newEelArray(newExpression, symbolName, instance); + newType = 'EelArray'; + break; } @@ -50,5 +58,5 @@ export const newExpression = ( } } - return newExpressionSrc; + return { newType, src: newExpressionSrc }; }; diff --git a/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.spec.ts b/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.spec.ts index a092940..8d44920 100644 --- a/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.spec.ts +++ b/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.spec.ts @@ -79,6 +79,36 @@ out_pin:In 1 outChannels: 2 }); +const someObj = { one: function myFunc() {} }; +`); + + expect(result.success).to.equal(false); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.7.0 */ + +desc:object_expression + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +`) + ); + expect(result.errors.length).to.equal(1); + expect(result.errors[0].type).to.equal('TypeError'); + }); + + it('literal value of wrong type', () => { + const compiler = new Js2EelCompiler(); + + const result = compiler.compile(`config({ + description: 'object_expression', + inChannels: 2, + outChannels: 2 +}); + const someObj = { one: 'somestring' }; `); diff --git a/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.ts b/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.ts index dbf4c28..06dde06 100644 --- a/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.ts +++ b/compiler/src/js2eel/generatorNodes/objectExpression/objectExpression.ts @@ -35,25 +35,40 @@ export const objectExpression = ( return null; } - if ( - property.value.type !== 'Literal' || - typeof property.value.value !== 'number' || - property.value.value === undefined - ) { - instance.error( - 'TypeError', - `Object property value must be number literal. ${property.value.type} not allowed`, - property.value - ); + switch (property.value.type) { + case 'Literal': { + if ( + typeof property.value.value !== 'number' || + property.value.value === undefined + ) { + instance.error( + 'TypeError', + `Literal object property value must be number. ${typeof property.value + .value} not allowed`, + property.value + ); - return null; - } + return null; + } + + object[property.key.name] = property.value.value; + eelSrc += `${objectIdentifier.name}__${suffixScopeByScopeSuffix( + property.key.name, + instance.getCurrentScopeSuffix() + )} = ${property.value.value};\n`; - object[property.key.name] = property.value.value; - eelSrc += `${objectIdentifier.name}__${suffixScopeByScopeSuffix( - property.key.name, - instance.getCurrentScopeSuffix() - )} = ${property.value.value};\n`; + break; + } + default: { + instance.error( + 'TypeError', + `Object property value is wrong type. ${property.value.type} not allowed`, + property.value + ); + + return null; + } + } } return { objectRepresentation: object, eelSrc: eelSrc }; diff --git a/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.spec.ts b/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.spec.ts index 8b3bd77..e10f418 100644 --- a/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.spec.ts +++ b/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.spec.ts @@ -271,6 +271,30 @@ out_pin:In 1 someVar = 4; +`) + ); + expect(result.errors.length).to.equal(0); + }); + + it('Right side can be conditional expression (ternary)', () => { + const compiler = new Js2EelCompiler(); + const result = + compiler.compile(`config({ description: 'variableDeclaration', inChannels: 2, outChannels: 2 }); + +let someVar = 4 < 5 ? 1 : 2; + `); + expect(testEelSrc(result.src)).to.equal( + testEelSrc(`/* Compiled with JS2EEL v0.9.1 */ + +desc:variableDeclaration + +in_pin:In 0 +in_pin:In 1 +out_pin:In 0 +out_pin:In 1 + + +someVar = 4 < 5 ? 1 : 2; `) ); expect(result.errors.length).to.equal(0); diff --git a/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.ts b/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.ts index 79c5623..d5cd2f8 100644 --- a/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.ts +++ b/compiler/src/js2eel/generatorNodes/variableDeclaration/variableDeclaration.ts @@ -2,6 +2,7 @@ import { literal } from '../literal/literal.js'; import { identifier } from '../identifier/identifier.js'; import { unaryExpression } from '../unaryExpression/unaryExpression.js'; import { binaryExpression } from '../binaryExpression/binaryExpression.js'; +import { conditionalExpression } from '../conditionalExpression/conditionalExpression.js'; import { newExpression } from '../newExpression/newExpression.js'; import { callExpression } from '../callExpression/callExpression.js'; import { memberExpression } from '../memberExpression/memberExpression.js'; @@ -29,6 +30,8 @@ export const variableDeclaration = ( let objectData: { objectRepresentation: ObjectRepresentation; eelSrc: string } | null = null; let isArrowFunctionDeclaration = false; + let isEelBuffer = false; + let isEelArray = false; // Because we inline user functions, we have to compose the compiled src from parts // let userFunctionBodySrc = ''; @@ -192,12 +195,15 @@ export const variableDeclaration = ( // Only happens for EelBuffers and EelArrays which we collect at the top // of the file. So we don't return compiled src here. - newExpression( + const { newType, src: _src } = newExpression( onlyDeclaration.init, (onlyDeclaration.id as Identifier).name, instance ); + isEelArray = newType === 'EelArray'; + isEelBuffer = newType === 'EelBuffer'; + // Gets printed at the end doNotPrint = true; @@ -232,6 +238,10 @@ export const variableDeclaration = ( break; } + case 'ConditionalExpression': { + rightSideSrc += conditionalExpression(onlyDeclaration.init, instance); + break; + } default: { instance.error( 'TypeError', @@ -298,11 +308,9 @@ export const variableDeclaration = ( if (onlyDeclaration.init) { newDeclaredSymbol.currentAssignment = { - type: 'variable', - eelSrc: '' + type: isEelArray ? 'EelArray' : isEelBuffer ? 'EelBuffer' : 'variable', + eelSrc: declarationSrc }; - - newDeclaredSymbol.currentAssignment.eelSrc = declarationSrc; } instance.setDeclaredSymbol(onlyDeclaration.id.name, newDeclaredSymbol); diff --git a/compiler/src/js2eel/suffixersAndPrefixers/prefixChannel.ts b/compiler/src/js2eel/suffixersAndPrefixers/prefixChannel.ts new file mode 100644 index 0000000..f3ac6e8 --- /dev/null +++ b/compiler/src/js2eel/suffixersAndPrefixers/prefixChannel.ts @@ -0,0 +1,11 @@ +export const prefixChannel = (channel: number): string => { + return 'CH__' + channel; +}; + +export const stripChannelPrefix = (channelSymbol: string): string => { + if (channelSymbol.startsWith('CH__')) { + return channelSymbol.slice(4); + } + + return channelSymbol; +}; diff --git a/compiler/src/js2eel/test/examplePlugins/01_volume.spec.ts b/compiler/src/js2eel/test/examplePlugins/01_volume.spec.ts index 4222acf..4deccea 100644 --- a/compiler/src/js2eel/test/examplePlugins/01_volume.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/01_volume.spec.ts @@ -36,10 +36,14 @@ target = 10 ^ (volume / (20)); /* Channel 0 */ +CH__0 = 0; + spl0 *= target; /* Channel 1 */ +CH__1 = 1; + spl1 *= target; diff --git a/compiler/src/js2eel/test/examplePlugins/02_sinewave.spec.ts b/compiler/src/js2eel/test/examplePlugins/02_sinewave.spec.ts index ac24e70..8a0498d 100644 --- a/compiler/src/js2eel/test/examplePlugins/02_sinewave.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/02_sinewave.spec.ts @@ -29,10 +29,14 @@ vol = (10 ^ (voldb / (20))); /* Channel 0 */ +CH__0 = 0; + spl0 = sin(2 * $pi * freq * t) * vol; /* Channel 1 */ +CH__1 = 1; + spl1 = sin(2 * $pi * freq * t) * vol; t += 1 / (srate); diff --git a/compiler/src/js2eel/test/examplePlugins/03_monoDelay.spec.ts b/compiler/src/js2eel/test/examplePlugins/03_monoDelay.spec.ts index 3b32a23..8c8d896 100644 --- a/compiler/src/js2eel/test/examplePlugins/03_monoDelay.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/03_monoDelay.spec.ts @@ -48,12 +48,16 @@ writeIndex = 0; /* Channel 0 */ +CH__0 = 0; + bufferValue = buffer__B0[readIndex]; buffer__B0[writeIndex] = spl0; spl0 = (spl0 + bufferValue * mix); /* Channel 1 */ +CH__1 = 1; + bufferValue = buffer__B1[readIndex]; buffer__B1[writeIndex] = spl1; spl1 = (spl1 + bufferValue * mix); diff --git a/compiler/src/js2eel/test/examplePlugins/04_stereoDelay.spec.ts b/compiler/src/js2eel/test/examplePlugins/04_stereoDelay.spec.ts index 9c34e2d..50eb72a 100644 --- a/compiler/src/js2eel/test/examplePlugins/04_stereoDelay.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/04_stereoDelay.spec.ts @@ -9,14 +9,16 @@ const JS_STEREO_DELAY_SRC = fs.readFileSync( 'utf-8' ); -const EEL_STEREO_DELAY_SRC_EXPECTED = `/* Compiled with JS2EEL v0.0.15 */ +const EEL_STEREO_DELAY_SRC_EXPECTED = `/* Compiled with JS2EEL v0.9.1 */ desc:stereo_delay -slider1:lengthMsL=120 < 0, 2000, 1 >Delay L / Mono (ms) -slider2:lengthMsR=120 < 0, 2000, 1 >Delay R (ms) -slider3:feedbackPercent=0 < 0, 100, 0.1 >Feedback (%) -slider4:mixDb=-6 < -120, 6, 0.01 >Mix (dB) +slider2:lengthMsL=120 < 0, 2000, 1 >Delay L / Mono (ms) +slider3:lengthMsR=120 < 0, 2000, 1 >Delay R (ms) +slider4:feedbackPercent=0 < 0, 100, 0.1 >Feedback (%) +slider5:mixDb=-6 < -120, 6, 0.01 >Mix (dB) + +slider1:type=0 < 0, 3, 1 {Mono, Stereo, Ping Pong} >Type in_pin:In 0 in_pin:In 1 @@ -26,6 +28,10 @@ out_pin:In 1 @init +numSamples__L = 0; +numSamples__R = 0; +bufferPos__L = 0; +bufferPos__R = 0; buffer__B0 = 0 * 400000; buffer__B1 = 1 * 400000; buffer__size = 400000; @@ -33,39 +39,35 @@ buffer__size = 400000; @slider -numSamples__D0__0 = lengthMsL * srate / (1000); -numSamples__D0__1 = lengthMsR * srate / (1000); feedback = feedbackPercent / (100); +numSamples__L = lengthMsL * srate / (1000); +numSamples__R = lengthMsR * srate / (1000); +(type == 0 || type == 2) ? ( +lengthMsR = lengthMsL; +); mix = 2 ^ (mixDb / (6)); @sample - -/* Channel 0 */ - -bufferValue__S6 = buffer__B0[bufferPos__D0__0]; -delayVal__S6 = min((spl0 + bufferValue__S6 * feedback), 1); -currentBufPos__S6 = bufferPos__D0__0; -buffer__B0[currentBufPos__S6] = delayVal__S6; -bufferPos__D0__0 = (currentBufPos__S6 + 1); -bufferPos__D0__0 >= numSamples__D0__0 ? ( -bufferPos__D0__0 = 0; +bufferValueL__S5 = buffer__B0[bufferPos__L]; +bufferValueR__S5 = buffer__B1[bufferPos__R]; +type == 2 ? ( +buffer__B1[bufferPos__R] = min((spl0 + bufferValueL__S5 * feedback), 1); +buffer__B0[bufferPos__L] = min((spl1 + bufferValueR__S5 * feedback), 1); +) : (buffer__B0[bufferPos__L] = min((spl0 + bufferValueL__S5 * feedback), 1); +buffer__B1[bufferPos__R] = min((spl1 + bufferValueR__S5 * feedback), 1); ); -spl0 = (spl0 + bufferValue__S6 * mix); - -/* Channel 1 */ - -bufferValue__S8 = buffer__B1[bufferPos__D0__1]; -delayVal__S8 = min((spl1 + bufferValue__S8 * feedback), 1); -currentBufPos__S8 = bufferPos__D0__1; -buffer__B1[currentBufPos__S8] = delayVal__S8; -bufferPos__D0__1 = (currentBufPos__S8 + 1); -bufferPos__D0__1 >= numSamples__D0__1 ? ( -bufferPos__D0__1 = 0; +bufferPos__L += 1; +bufferPos__R += 1; +bufferPos__L >= numSamples__L ? ( +bufferPos__L = 0; ); -spl1 = (spl1 + bufferValue__S8 * mix); - +bufferPos__R >= numSamples__R ? ( +bufferPos__R = 0; +); +spl0 = (spl0 + bufferValueL__S5 * mix); +spl1 = (spl1 + bufferValueR__S5 * mix); `; diff --git a/compiler/src/js2eel/test/examplePlugins/05_lowpass.spec.ts b/compiler/src/js2eel/test/examplePlugins/05_lowpass.spec.ts index dd043cd..a3c7889 100644 --- a/compiler/src/js2eel/test/examplePlugins/05_lowpass.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/05_lowpass.spec.ts @@ -55,6 +55,8 @@ outputGain = (10 ^ (outputGainDb / (20))); /* Channel 0 */ +CH__0 = 0; + lpFreq < 22000 ? ( lpYStore__D0__0 = ((((lpCoefs__b0x * lpXStore__D0__0 + lpCoefs__b1x * lpXStore__D0__1) + lpCoefs__b2x * lpXStore__D0__2) - lpCoefs__a1x * lpYStore__D0__1) - lpCoefs__a2x * lpYStore__D0__2); lpYStore__D0__2 = lpYStore__D0__1; @@ -69,6 +71,8 @@ spl0 = spl0 * outputGain; /* Channel 1 */ +CH__1 = 1; + lpFreq < 22000 ? ( lpYStore__D1__0 = ((((lpCoefs__b0x * lpXStore__D1__0 + lpCoefs__b1x * lpXStore__D1__1) + lpCoefs__b2x * lpXStore__D1__2) - lpCoefs__a1x * lpYStore__D1__1) - lpCoefs__a2x * lpYStore__D1__2); lpYStore__D1__2 = lpYStore__D1__1; diff --git a/compiler/src/js2eel/test/examplePlugins/06_saturation.spec.ts b/compiler/src/js2eel/test/examplePlugins/06_saturation.spec.ts index 2600587..9404e0e 100644 --- a/compiler/src/js2eel/test/examplePlugins/06_saturation.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/06_saturation.spec.ts @@ -32,6 +32,8 @@ volume = 10 ^ (volumeDb / (20)); /* Channel 0 */ +CH__0 = 0; + algorithm == 0 ? ( spl0 = (2 * 1 / ((1 + exp(-gainIn * spl0))) - 1); ) : (algorithm == 1 ? ( @@ -46,6 +48,8 @@ spl0 *= volume; /* Channel 1 */ +CH__1 = 1; + algorithm == 0 ? ( spl1 = (2 * 1 / ((1 + exp(-gainIn * spl1))) - 1); ) : (algorithm == 1 ? ( diff --git a/compiler/src/js2eel/test/examplePlugins/07_4band_eq.spec.ts b/compiler/src/js2eel/test/examplePlugins/07_4band_eq.spec.ts index 7cf4acd..8f0190b 100644 --- a/compiler/src/js2eel/test/examplePlugins/07_4band_eq.spec.ts +++ b/compiler/src/js2eel/test/examplePlugins/07_4band_eq.spec.ts @@ -225,6 +225,8 @@ outputGain = (10 ^ (outputGainDb / (20))); /* Channel 0 */ +CH__0 = 0; + lpFreq < 22000 ? ( lpYStore__D0__0 = ((((lpCoefs__b0x * lpXStore__D0__0 + lpCoefs__b1x * lpXStore__D0__1) + lpCoefs__b2x * lpXStore__D0__2) - lpCoefs__a1x * lpYStore__D0__1) - lpCoefs__a2x * lpYStore__D0__2); lpYStore__D0__2 = lpYStore__D0__1; @@ -309,6 +311,8 @@ spl0 = spl0 * outputGain; /* Channel 1 */ +CH__1 = 1; + lpFreq < 22000 ? ( lpYStore__D1__0 = ((((lpCoefs__b0x * lpXStore__D1__0 + lpCoefs__b1x * lpXStore__D1__1) + lpCoefs__b2x * lpXStore__D1__2) - lpCoefs__a1x * lpYStore__D1__1) - lpCoefs__a2x * lpYStore__D1__2); lpYStore__D1__2 = lpYStore__D1__1; @@ -397,8 +401,8 @@ spl1 = spl1 * outputGain; const js2EelCompiler = new Js2EelCompiler(); -describe('Example Test: Saturation', () => { - it('Compiles saturation plugin with select box', () => { +describe('Example Test: 4 Band EQ', () => { + it('Compiles 4 band eq plugin', () => { const result = js2EelCompiler.compile(JS_4BAND_EQ_SRC); expect(result.success).to.equal(true); diff --git a/compiler/src/js2eel/types.ts b/compiler/src/js2eel/types.ts index 3512c3b..6b94a0d 100644 --- a/compiler/src/js2eel/types.ts +++ b/compiler/src/js2eel/types.ts @@ -139,6 +139,13 @@ export type ObjectAssignment = { value: ObjectRepresentation; }; +export type EelNewExpressionType = 'EelBuffer' | 'EelArray'; + +export type EelSymbolAssignment = { + type: EelNewExpressionType; + eelSrc: string; +}; + export type DeclaredSymbol = { used: boolean; declarationType: AllowedDeclarationType; @@ -146,10 +153,19 @@ export type DeclaredSymbol = { inScopeSuffix: number; name: string; node: Node | null | undefined; - currentAssignment: null | VariableAssignment | FunctionAssignment | ObjectAssignment; + currentAssignment: + | null + | VariableAssignment + | FunctionAssignment + | ObjectAssignment + | EelSymbolAssignment; }; export type ResultDeclaredSymbol = DeclaredSymbol & { - currentAssignment: VariableAssignment | ResultFunctionAssignment | ObjectAssignment; + currentAssignment: + | VariableAssignment + | ResultFunctionAssignment + | ObjectAssignment + | EelSymbolAssignment; }; export type Slider = { diff --git a/examples/04_stereo_delay.js b/examples/04_stereo_delay.js index 543e553..4e4af0a 100644 --- a/examples/04_stereo_delay.js +++ b/examples/04_stereo_delay.js @@ -1,5 +1,6 @@ config({ description: 'stereo_delay', inChannels: 2, outChannels: 2 }); +let type; let lengthMsL; let lengthMsR; let mixDb; @@ -8,35 +9,67 @@ let feedbackPercent; let mix; const buffer = new EelBuffer(2, 400000); -const numSamples = new EelArray(1, 2); -const bufferPos = new EelArray(1, 2); -slider(1, lengthMsL, 120, 0, 2000, 1, 'Delay L / Mono (ms)'); -slider(2, lengthMsR, 120, 0, 2000, 1, 'Delay R (ms)'); -slider(3, feedbackPercent, 0, 0, 100, 0.1, 'Feedback (%)'); -slider(4, mixDb, -6, -120, 6, 0.01, 'Mix (dB)'); +const numSamples = { + L: 0, + R: 0 +}; +const bufferPos = { + L: 0, + R: 0 +}; -onSlider(() => { - numSamples[0][0] = (lengthMsL * srate) / 1000; - numSamples[0][1] = (lengthMsR * srate) / 1000; +selectBox( + 1, + type, + 'mono', + [ + { name: 'mono', label: 'Mono' }, + { name: 'stereo', label: 'Stereo' }, + { name: 'pingpong', label: 'Ping Pong' } + ], + 'Type' +); +slider(2, lengthMsL, 120, 0, 2000, 1, 'Delay L / Mono (ms)'); +slider(3, lengthMsR, 120, 0, 2000, 1, 'Delay R (ms)'); +slider(4, feedbackPercent, 0, 0, 100, 0.1, 'Feedback (%)'); +slider(5, mixDb, -6, -120, 6, 0.01, 'Mix (dB)'); +onSlider(() => { feedback = feedbackPercent / 100; + numSamples.L = (lengthMsL * srate) / 1000; + numSamples.R = (lengthMsR * srate) / 1000; + + if (type === 'mono' || type === 'pingpong') { + lengthMsR = lengthMsL; + } + mix = Math.pow(2, mixDb / 6); }); onSample(() => { - eachChannel((sample, ch) => { - const bufferValue = buffer[ch][bufferPos[0][ch]]; - const delayVal = min(sample + bufferValue * feedback, 1); - const currentBufPos = bufferPos[0][ch]; - buffer[ch][currentBufPos] = delayVal; - bufferPos[0][ch] = currentBufPos + 1; - - if (bufferPos[0][ch] >= numSamples[0][ch]) { - bufferPos[0][ch] = 0; - } - - sample = sample + bufferValue * mix; - }); + const bufferValueL = buffer[0][bufferPos.L]; + const bufferValueR = buffer[1][bufferPos.R]; + + if (type === 'pingpong') { + buffer[1][bufferPos.R] = min(spl0 + bufferValueL * feedback, 1); + buffer[0][bufferPos.L] = min(spl1 + bufferValueR * feedback, 1); + } else { + buffer[0][bufferPos.L] = min(spl0 + bufferValueL * feedback, 1); + buffer[1][bufferPos.R] = min(spl1 + bufferValueR * feedback, 1); + } + + bufferPos.L += 1; + bufferPos.R += 1; + + if (bufferPos.L >= numSamples.L) { + bufferPos.L = 0; + } + if (bufferPos.R >= numSamples.R) { + bufferPos.R = 0; + } + + spl0 = spl0 + bufferValueL * mix; + spl1 = spl1 + bufferValueR * mix; }); diff --git a/gui/src/components/js2eel/examples/04_stereo_delay.ts b/gui/src/components/js2eel/examples/04_stereo_delay.ts index 0700882..258cd71 100644 --- a/gui/src/components/js2eel/examples/04_stereo_delay.ts +++ b/gui/src/components/js2eel/examples/04_stereo_delay.ts @@ -2,6 +2,7 @@ export const EXAMPLE_STEREO_DELAY_JS = { path: 'example://stereo_delay.js', src: `config({ description: 'stereo_delay', inChannels: 2, outChannels: 2 }); +let type; let lengthMsL; let lengthMsR; let mixDb; @@ -10,37 +11,69 @@ let feedbackPercent; let mix; const buffer = new EelBuffer(2, 400000); -const numSamples = new EelArray(1, 2); -const bufferPos = new EelArray(1, 2); -slider(1, lengthMsL, 120, 0, 2000, 1, 'Delay L / Mono (ms)'); -slider(2, lengthMsR, 120, 0, 2000, 1, 'Delay R (ms)'); -slider(3, feedbackPercent, 0, 0, 100, 0.1, 'Feedback (%)'); -slider(4, mixDb, -6, -120, 6, 0.01, 'Mix (dB)'); +const numSamples = { + L: 0, + R: 0 +}; +const bufferPos = { + L: 0, + R: 0 +}; -onSlider(() => { - numSamples[0][0] = (lengthMsL * srate) / 1000; - numSamples[0][1] = (lengthMsR * srate) / 1000; +selectBox( + 1, + type, + 'mono', + [ + { name: 'mono', label: 'Mono' }, + { name: 'stereo', label: 'Stereo' }, + { name: 'pingpong', label: 'Ping Pong' } + ], + 'Type' +); +slider(2, lengthMsL, 120, 0, 2000, 1, 'Delay L / Mono (ms)'); +slider(3, lengthMsR, 120, 0, 2000, 1, 'Delay R (ms)'); +slider(4, feedbackPercent, 0, 0, 100, 0.1, 'Feedback (%)'); +slider(5, mixDb, -6, -120, 6, 0.01, 'Mix (dB)'); +onSlider(() => { feedback = feedbackPercent / 100; + numSamples.L = (lengthMsL * srate) / 1000; + numSamples.R = (lengthMsR * srate) / 1000; + + if (type === 'mono' || type === 'pingpong') { + lengthMsR = lengthMsL; + } + mix = Math.pow(2, mixDb / 6); }); onSample(() => { - eachChannel((sample, ch) => { - const bufferValue = buffer[ch][bufferPos[0][ch]]; - const delayVal = min(sample + bufferValue * feedback, 1); - const currentBufPos = bufferPos[0][ch]; - buffer[ch][currentBufPos] = delayVal; - bufferPos[0][ch] = currentBufPos + 1; - - if (bufferPos[0][ch] >= numSamples[0][ch]) { - bufferPos[0][ch] = 0; - } - - sample = sample + bufferValue * mix; - }); + const bufferValueL = buffer[0][bufferPos.L]; + const bufferValueR = buffer[1][bufferPos.R]; + + if (type === 'pingpong') { + buffer[1][bufferPos.R] = min(spl0 + bufferValueL * feedback, 1); + buffer[0][bufferPos.L] = min(spl1 + bufferValueR * feedback, 1); + } else { + buffer[0][bufferPos.L] = min(spl0 + bufferValueL * feedback, 1); + buffer[1][bufferPos.R] = min(spl1 + bufferValueR * feedback, 1); + } + + bufferPos.L += 1; + bufferPos.R += 1; + + if (bufferPos.L >= numSamples.L) { + bufferPos.L = 0; + } + if (bufferPos.R >= numSamples.R) { + bufferPos.R = 0; + } + + spl0 = spl0 + bufferValueL * mix; + spl1 = spl1 + bufferValueR * mix; }); ` };