Files
modeling-app/src/lang/modifyAst.ts

517 lines
12 KiB
TypeScript
Raw Normal View History

import {
Program,
BlockStatement,
SketchExpression,
CallExpression,
PipeExpression,
VariableDeclaration,
ExpressionStatement,
Value,
getNodeFromPath,
2023-01-06 12:45:34 +11:00
VariableDeclarator,
} from './abstractSyntaxTree'
import { PathToNode } from './executor'
export function addSketchTo(
node: Program,
axis: 'xy' | 'xz' | 'yz',
name = ''
): { modifiedAst: Program; id: string; pathToNode: (string | number)[] } {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const _name = name || findUniqueName(node, 'part')
const sketchBody: BlockStatement = {
type: 'BlockStatement',
...dumbyStartend,
body: [],
}
const sketch: SketchExpression = {
type: 'SketchExpression',
...dumbyStartend,
body: sketchBody,
}
const rotate: CallExpression = {
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: axis === 'xz' ? 'rx' : 'ry',
},
arguments: [
{
type: 'Literal',
...dumbyStartend,
value: axis === 'yz' ? 90 : 90,
raw: axis === 'yz' ? '90' : '90',
},
{
type: 'PipeSubstitution',
...dumbyStartend,
},
],
optional: false,
}
const pipChain: PipeExpression = {
type: 'PipeExpression',
...dumbyStartend,
body: [sketch, rotate],
}
const sketchVariableDeclaration: VariableDeclaration = {
type: 'VariableDeclaration',
...dumbyStartend,
kind: 'sketch',
declarations: [
{
type: 'VariableDeclarator',
...dumbyStartend,
id: {
type: 'Identifier',
...dumbyStartend,
name: _name,
},
init: axis === 'xy' ? sketch : pipChain,
},
],
}
const showCallIndex = getShowIndex(_node)
let sketchIndex = showCallIndex
if (showCallIndex === -1) {
_node.body = [...node.body, sketchVariableDeclaration]
sketchIndex = _node.body.length - 1
} else {
const newBody = [...node.body]
newBody.splice(showCallIndex, 0, sketchVariableDeclaration)
_node.body = newBody
}
let pathToNode: (string | number)[] = [
'body',
sketchIndex,
'declarations',
'0',
'init',
]
if (axis !== 'xy') {
pathToNode = [...pathToNode, 'body', '0']
}
return {
modifiedAst: addToShow(_node, _name),
id: _name,
pathToNode,
}
}
function findUniqueName(
ast: Program | string,
name: string,
pad = 3,
index = 1
): string {
let searchStr = ''
if (typeof ast === 'string') {
searchStr = ast
} else {
searchStr = JSON.stringify(ast)
}
const indexStr = `${index}`.padStart(pad, '0')
const newName = `${name}${indexStr}`
const isInString = searchStr.includes(newName)
if (!isInString) {
return newName
}
return findUniqueName(searchStr, name, pad, index + 1)
}
function addToShow(node: Program, name: string): Program {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const showCallIndex = getShowIndex(_node)
if (showCallIndex === -1) {
const showCall: CallExpression = {
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'show',
},
optional: false,
arguments: [
{
type: 'Identifier',
...dumbyStartend,
name,
},
],
}
const showExpressionStatement: ExpressionStatement = {
type: 'ExpressionStatement',
...dumbyStartend,
expression: showCall,
}
_node.body = [..._node.body, showExpressionStatement]
return _node
}
const showCall = { ..._node.body[showCallIndex] } as ExpressionStatement
const showCallArgs = (showCall.expression as CallExpression).arguments
const newShowCallArgs: Value[] = [
...showCallArgs,
{
type: 'Identifier',
...dumbyStartend,
name,
},
]
const newShowExpression: CallExpression = {
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'show',
},
optional: false,
arguments: newShowCallArgs,
}
_node.body[showCallIndex] = {
...showCall,
expression: newShowExpression,
}
return _node
}
function getShowIndex(node: Program): number {
return node.body.findIndex(
(statement) =>
statement.type === 'ExpressionStatement' &&
statement.expression.type === 'CallExpression' &&
statement.expression.callee.type === 'Identifier' &&
statement.expression.callee.name === 'show'
)
}
export function addLine(
node: Program,
pathToNode: (string | number)[],
to: [number, number]
): { modifiedAst: Program; pathToNode: (string | number)[] } {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const sketchExpression = getNodeFromPath(
_node,
pathToNode,
'SketchExpression'
) as SketchExpression
const line: ExpressionStatement = {
type: 'ExpressionStatement',
...dumbyStartend,
expression: {
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'lineTo',
},
optional: false,
arguments: [
{
type: 'Literal',
...dumbyStartend,
value: to[0],
raw: `${to[0]}`,
},
{
type: 'Literal',
...dumbyStartend,
value: to[1],
raw: `${to[1]}`,
},
],
},
}
const newBody = [...sketchExpression.body.body, line]
sketchExpression.body.body = newBody
return {
modifiedAst: _node,
pathToNode,
}
}
export function changeArguments(
node: Program,
pathToNode: (string | number)[],
args: [number, number]
): { modifiedAst: Program; pathToNode: (string | number)[] } {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
// const thePath = getNodePathFromSourceRange(_node, sourceRange)
const callExpression = getNodeFromPath(_node, pathToNode) as CallExpression
const newXArg: CallExpression['arguments'][number] =
callExpression.arguments[0].type === 'Literal'
? {
type: 'Literal',
...dumbyStartend,
value: args[0],
raw: `${args[0]}`,
}
: {
...callExpression.arguments[0],
}
const newYArg: CallExpression['arguments'][number] =
callExpression.arguments[1].type === 'Literal'
? {
type: 'Literal',
...dumbyStartend,
value: args[1],
raw: `${args[1]}`,
}
: {
...callExpression.arguments[1],
}
callExpression.arguments = [newXArg, newYArg]
return {
modifiedAst: _node,
pathToNode,
}
}
2023-01-06 12:45:34 +11:00
export function extrudeSketch(
node: Program,
pathToNode: (string | number)[],
shouldPipe = true
): { modifiedAst: Program; pathToNode: (string | number)[] } {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const sketchExpression = getNodeFromPath(
_node,
pathToNode,
'SketchExpression'
) as SketchExpression
// determine if sketchExpression is in a pipeExpression or not
const pipeExpression = getNodeFromPath(_node, pathToNode, 'PipeExpression')
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
const variableDeclorator = getNodeFromPath(
_node,
pathToNode,
'VariableDeclarator'
) as VariableDeclarator
const extrudeCall: CallExpression = {
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'extrude',
},
optional: false,
arguments: [
{
type: 'Literal',
...dumbyStartend,
value: 4,
raw: '4',
},
shouldPipe
? {
type: 'PipeSubstitution',
...dumbyStartend,
}
: {
type: 'Identifier',
...dumbyStartend,
name: variableDeclorator.id.name,
},
],
}
if (shouldPipe) {
const pipeChain: PipeExpression = isInPipeExpression
? {
type: 'PipeExpression',
...dumbyStartend,
body: [...pipeExpression.body, extrudeCall],
}
: {
type: 'PipeExpression',
...dumbyStartend,
body: [sketchExpression, extrudeCall],
}
variableDeclorator.init = pipeChain
return {
modifiedAst: _node,
pathToNode,
}
}
const name = findUniqueName(node, 'part')
const VariableDeclaration: VariableDeclaration = {
type: 'VariableDeclaration',
...dumbyStartend,
declarations: [
{
type: 'VariableDeclarator',
...dumbyStartend,
id: {
type: 'Identifier',
...dumbyStartend,
name,
},
init: extrudeCall,
},
],
kind: 'const',
}
const showCallIndex = getShowIndex(_node)
_node.body.splice(showCallIndex, 0, VariableDeclaration)
return {
modifiedAst: addToShow(_node, name),
pathToNode: [...pathToNode.slice(0, -1), showCallIndex],
}
}
export function sketchOnExtrudedFace(
node: Program,
pathToNode: (string | number)[]
): { modifiedAst: Program; pathToNode: (string | number)[] } {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const newSketchName = findUniqueName(node, 'part')
2023-01-10 15:40:34 +11:00
const oldSketchName = getNodeFromPath(
_node,
pathToNode,
'VariableDeclarator',
true
).id.name
const expression = getNodeFromPath(_node, pathToNode, 'CallExpression') as
| VariableDeclarator
| CallExpression
const pathName =
expression.type === 'VariableDeclarator'
? expression.id.name
: findUniqueName(node, 'path', 2)
if (expression.type === 'CallExpression') {
const block = getNodeFromPath(_node, pathToNode, 'BlockStatement')
const expressionIndex = getLastIndex(pathToNode)
if (expression.callee.name !== 'lineTo')
throw new Error('expected a lineTo call')
const newExpression: VariableDeclaration = {
type: 'VariableDeclaration',
...dumbyStartend,
declarations: [
{
type: 'VariableDeclarator',
...dumbyStartend,
id: {
type: 'Identifier',
...dumbyStartend,
name: pathName,
},
init: expression,
},
],
kind: 'path',
}
block.body.splice(expressionIndex, 1, newExpression)
}
// create pipe expression with a sketch block piped into a transform function
const sketchPipe: PipeExpression = {
type: 'PipeExpression',
...dumbyStartend,
body: [
{
type: 'SketchExpression',
...dumbyStartend,
body: {
type: 'BlockStatement',
...dumbyStartend,
body: [],
},
},
{
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'transform',
},
optional: false,
arguments: [
{
type: 'CallExpression',
...dumbyStartend,
callee: {
type: 'Identifier',
...dumbyStartend,
name: 'getExtrudeWallTransform',
},
optional: false,
arguments: [
{
type: 'Literal',
...dumbyStartend,
value: pathName,
raw: `'${pathName}'`,
},
{
type: 'Identifier',
...dumbyStartend,
name: oldSketchName,
},
],
},
{
type: 'PipeSubstitution',
...dumbyStartend,
},
],
},
],
}
const variableDec: VariableDeclaration = {
type: 'VariableDeclaration',
...dumbyStartend,
declarations: [
{
type: 'VariableDeclarator',
...dumbyStartend,
id: {
type: 'Identifier',
...dumbyStartend,
name: newSketchName,
},
init: sketchPipe,
},
],
kind: 'sketch',
}
const showIndex = getShowIndex(_node)
_node.body.splice(showIndex, 0, variableDec)
return {
modifiedAst: addToShow(_node, newSketchName),
pathToNode,
}
}
const getLastIndex = (pathToNode: PathToNode): number => {
const last = pathToNode[pathToNode.length - 1]
if (typeof last === 'number') {
return last
}
return getLastIndex(pathToNode.slice(0, -1))
}