From ed50da52f7cd7170dd2e27d1cb4331eb992d429e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 20 Feb 2022 20:29:07 +0100 Subject: [PATCH] add locations to primitive pattern args --- repl/src/shapeshifter.js | 171 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 6 deletions(-) diff --git a/repl/src/shapeshifter.js b/repl/src/shapeshifter.js index b55d516a..789645c1 100644 --- a/repl/src/shapeshifter.js +++ b/repl/src/shapeshifter.js @@ -1,30 +1,189 @@ -import { parseScript } from './shift-parser/index.js'; // npm module does not work in the browser +import { parseScriptWithLocation } from './shift-parser/index.js'; // npm module does not work in the browser import traverser from './shift-traverser'; // npm module does not work in the browser const { replace } = traverser; -import { LiteralStringExpression, IdentifierExpression } from 'shift-ast'; +import { LiteralStringExpression, IdentifierExpression, CallExpression, StaticMemberExpression } from 'shift-ast'; import codegen from 'shift-codegen'; +import * as strudel from '../../strudel.mjs'; + +const { Pattern } = strudel; const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name); +const addLocations = true; + export default (code) => { - const ast = parseScript(code); - const shifted = replace(ast, { + const ast = parseScriptWithLocation(code); + const nodesWithLocation = []; + const parents = []; + const shifted = replace(ast.tree, { enter(node, parent) { - // replace identifiers that are a note with a note string + parents.push(parent); + const isSynthetic = parents.some((p) => nodesWithLocation.includes(p)); + if (isSynthetic) { + return node; + } + const grandparent = parents[parents.length - 2]; + const isPatternArg = (parent) => + parent?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(parent.callee.name); + const isTimeCat = parent?.type === 'ArrayExpression' && isPatternArg(grandparent); + const isMarkable = isPatternArg(parent) || isTimeCat; + // replace pseudo note variables if (node.type === 'IdentifierExpression') { if (isNote(node.name)) { const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name; + if (addLocations && isMarkable) { + return addPureWithLocation(value, node, ast.locations, nodesWithLocation); + } return new LiteralStringExpression({ value }); } if (node.name === 'r') { return new IdentifierExpression({ name: 'silence' }); } } + if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) { + // console.log('add', node); + return addPureWithLocation(node.value, node, ast.locations, nodesWithLocation); + } + return node; + }, + leave() { + parents.pop(); }, }); return codegen(shifted); }; +// turns node in pure(value).withLocation(location), where location is the node's location in the source code +// with this, the pure pattern can pass its location to the event, to know where to highlight when it's active +function addPureWithLocation(value, node, locations, nodesWithLocation) { + // console.log('addPure', value, node); + const withLocation = new CallExpression({ + callee: new StaticMemberExpression({ + object: new CallExpression({ + callee: new IdentifierExpression({ + name: 'pure', + }), + arguments: [new LiteralStringExpression({ value })], + }), + property: 'withLocation', + }), + arguments: [getLocationObject(node, locations)], + }); + nodesWithLocation.push(withLocation); + return withLocation; +} + +// returns ast for source location object +function getLocationObject(node, locations) { + /*const locationAST = parseScript( + "x=" + JSON.stringify(ast.locations.get(node)) + ).statements[0].expression.expression; + + console.log("locationAST", locationAST);*/ + + /*const callAST = parseScript( + `pure(${node.name}).withLocation(${JSON.stringify( + ast.locations.get(node) + )})` + ).statements[0].expression;*/ + const loc = locations.get(node); + return { + type: 'ObjectExpression', + properties: [ + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'start', + }, + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'line', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.start.line, + }, + }, + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'column', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.start.column, + }, + }, + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'offset', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.start.offset, + }, + }, + ], + }, + }, + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'end', + }, + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'line', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.end.line, + }, + }, + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'column', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.end.column, + }, + }, + { + type: 'DataProperty', + name: { + type: 'StaticPropertyName', + value: 'offset', + }, + expression: { + type: 'LiteralNumericExpression', + value: loc.end.offset, + }, + }, + ], + }, + }, + ], + }; +} + // TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m) // and ['c1*2'].xx into 'c1*2'.m.xx ?? -// or just all templated strings?? x.groove(`[~ x]*2`) \ No newline at end of file +// or just all templated strings?? x.groove(`[~ x]*2`)