2022-11-26 08:34:23 +11:00
|
|
|
import { Token } from './tokeniser'
|
2022-11-13 11:14:30 +11:00
|
|
|
|
|
|
|
type syntaxType =
|
2022-11-26 08:34:23 +11:00
|
|
|
| 'Program'
|
|
|
|
| 'ExpressionStatement'
|
|
|
|
| 'BinaryExpression'
|
|
|
|
| 'NumberLiteral'
|
|
|
|
| 'StringLiteral'
|
|
|
|
| 'CallExpression'
|
|
|
|
| 'Identifier'
|
|
|
|
| 'BlockStatement'
|
|
|
|
| 'IfStatement'
|
|
|
|
| 'WhileStatement'
|
|
|
|
| 'FunctionDeclaration'
|
|
|
|
| 'ReturnStatement'
|
|
|
|
| 'VariableDeclaration'
|
|
|
|
| 'VariableDeclarator'
|
|
|
|
| 'AssignmentExpression'
|
|
|
|
| 'UnaryExpression'
|
|
|
|
| 'MemberExpression'
|
|
|
|
| 'ArrayExpression'
|
|
|
|
| 'ObjectExpression'
|
|
|
|
| 'Property'
|
|
|
|
| 'LogicalExpression'
|
|
|
|
| 'ConditionalExpression'
|
|
|
|
| 'ForStatement'
|
|
|
|
| 'ForInStatement'
|
|
|
|
| 'ForOfStatement'
|
|
|
|
| 'BreakStatement'
|
|
|
|
| 'ContinueStatement'
|
|
|
|
| 'SwitchStatement'
|
|
|
|
| 'SwitchCase'
|
|
|
|
| 'ThrowStatement'
|
|
|
|
| 'TryStatement'
|
|
|
|
| 'CatchClause'
|
|
|
|
| 'ClassDeclaration'
|
|
|
|
| 'ClassBody'
|
|
|
|
| 'MethodDefinition'
|
|
|
|
| 'NewExpression'
|
|
|
|
| 'ThisExpression'
|
|
|
|
| 'UpdateExpression'
|
2022-11-17 20:17:00 +11:00
|
|
|
// | "ArrowFunctionExpression"
|
2022-11-26 08:34:23 +11:00
|
|
|
| 'FunctionExpression'
|
|
|
|
| 'SketchExpression'
|
|
|
|
| 'YieldExpression'
|
|
|
|
| 'AwaitExpression'
|
|
|
|
| 'ImportDeclaration'
|
|
|
|
| 'ImportSpecifier'
|
|
|
|
| 'ImportDefaultSpecifier'
|
|
|
|
| 'ImportNamespaceSpecifier'
|
|
|
|
| 'ExportNamedDeclaration'
|
|
|
|
| 'ExportDefaultDeclaration'
|
|
|
|
| 'ExportAllDeclaration'
|
|
|
|
| 'ExportSpecifier'
|
|
|
|
| 'TaggedTemplateExpression'
|
|
|
|
| 'TemplateLiteral'
|
|
|
|
| 'TemplateElement'
|
|
|
|
| 'SpreadElement'
|
|
|
|
| 'RestElement'
|
|
|
|
| 'SequenceExpression'
|
|
|
|
| 'DebuggerStatement'
|
|
|
|
| 'LabeledStatement'
|
|
|
|
| 'DoWhileStatement'
|
|
|
|
| 'WithStatement'
|
|
|
|
| 'EmptyStatement'
|
|
|
|
| 'Literal'
|
|
|
|
| 'ArrayPattern'
|
|
|
|
| 'ObjectPattern'
|
|
|
|
| 'AssignmentPattern'
|
|
|
|
| 'MetaProperty'
|
|
|
|
| 'Super'
|
|
|
|
| 'Import'
|
|
|
|
| 'RegExpLiteral'
|
|
|
|
| 'BooleanLiteral'
|
|
|
|
| 'NullLiteral'
|
|
|
|
| 'TypeAnnotation'
|
2022-11-13 11:14:30 +11:00
|
|
|
|
|
|
|
export interface Program {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: syntaxType
|
|
|
|
start: number
|
|
|
|
end: number
|
|
|
|
body: Body[]
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
interface GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: syntaxType
|
|
|
|
start: number
|
|
|
|
end: number
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
interface ExpressionStatement extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'ExpressionStatement'
|
|
|
|
expression: Value
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeExpressionStatement(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
2022-11-17 20:17:00 +11:00
|
|
|
): { expression: ExpressionStatement; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const { token: nextToken } = nextMeaningfulToken(tokens, index)
|
|
|
|
if (nextToken.type === 'brace' && nextToken.value === '(') {
|
|
|
|
const { expression, lastIndex } = makeCallExpression(tokens, index)
|
2022-11-14 13:28:16 +11:00
|
|
|
return {
|
2022-11-17 20:17:00 +11:00
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'ExpressionStatement',
|
2022-11-17 20:17:00 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: expression.end,
|
|
|
|
expression,
|
|
|
|
},
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 08:34:23 +11:00
|
|
|
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
2022-11-17 20:17:00 +11:00
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'ExpressionStatement',
|
2022-11-17 20:17:00 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: expression.end,
|
|
|
|
expression,
|
|
|
|
},
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:03:09 +11:00
|
|
|
export interface CallExpression extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'CallExpression'
|
|
|
|
callee: Identifier
|
|
|
|
arguments: Value[]
|
|
|
|
optional: boolean
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeCallExpression(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): {
|
2022-11-26 08:34:23 +11:00
|
|
|
expression: CallExpression
|
|
|
|
lastIndex: number
|
2022-11-14 13:28:16 +11:00
|
|
|
} {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const braceToken = nextMeaningfulToken(tokens, index)
|
2022-11-14 13:28:16 +11:00
|
|
|
// const firstArgumentToken = nextMeaningfulToken(tokens, braceToken.index);
|
2022-11-26 08:34:23 +11:00
|
|
|
const callee = makeIdentifier(tokens, index)
|
|
|
|
const args = makeArguments(tokens, braceToken.index)
|
2022-11-14 13:28:16 +11:00
|
|
|
// const closingBraceToken = nextMeaningfulToken(tokens, args.lastIndex);
|
2022-11-26 08:34:23 +11:00
|
|
|
const closingBraceToken = tokens[args.lastIndex]
|
2022-11-14 13:28:16 +11:00
|
|
|
return {
|
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'CallExpression',
|
2022-11-14 13:28:16 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: closingBraceToken.end,
|
|
|
|
callee,
|
|
|
|
arguments: args.arguments,
|
|
|
|
optional: false,
|
|
|
|
},
|
|
|
|
lastIndex: args.lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeArguments(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
2022-11-18 08:20:18 +11:00
|
|
|
previousArgs: Value[] = []
|
2022-11-14 13:28:16 +11:00
|
|
|
): {
|
2022-11-26 08:34:23 +11:00
|
|
|
arguments: Value[]
|
|
|
|
lastIndex: number
|
2022-11-14 13:28:16 +11:00
|
|
|
} {
|
2022-11-26 08:34:23 +11:00
|
|
|
const braceOrCommaToken = tokens[index]
|
|
|
|
const argumentToken = nextMeaningfulToken(tokens, index)
|
2022-11-17 20:17:00 +11:00
|
|
|
const shouldFinishRecursion =
|
2022-11-26 08:34:23 +11:00
|
|
|
braceOrCommaToken.type === 'brace' && braceOrCommaToken.value === ')'
|
2022-11-14 13:28:16 +11:00
|
|
|
if (shouldFinishRecursion) {
|
|
|
|
return {
|
|
|
|
arguments: previousArgs,
|
|
|
|
lastIndex: index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
|
2022-11-17 20:17:00 +11:00
|
|
|
const isIdentifierOrLiteral =
|
2022-11-26 08:34:23 +11:00
|
|
|
nextBraceOrCommaToken.token.type === 'comma' ||
|
|
|
|
nextBraceOrCommaToken.token.type === 'brace'
|
2022-11-14 13:28:16 +11:00
|
|
|
if (!isIdentifierOrLiteral) {
|
2022-11-26 08:34:23 +11:00
|
|
|
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
|
|
|
|
return makeArguments(tokens, lastIndex, [...previousArgs, expression])
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (argumentToken.token.type === 'word') {
|
|
|
|
const identifier = makeIdentifier(tokens, argumentToken.index)
|
2022-11-14 13:28:16 +11:00
|
|
|
return makeArguments(tokens, nextBraceOrCommaToken.index, [
|
|
|
|
...previousArgs,
|
|
|
|
identifier,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
2022-11-14 13:28:16 +11:00
|
|
|
} else if (
|
2022-11-26 08:34:23 +11:00
|
|
|
argumentToken.token.type === 'number' ||
|
|
|
|
argumentToken.token.type === 'string'
|
2022-11-14 13:28:16 +11:00
|
|
|
) {
|
2022-11-26 08:34:23 +11:00
|
|
|
const literal = makeLiteral(tokens, argumentToken.index)
|
2022-11-17 20:17:00 +11:00
|
|
|
return makeArguments(tokens, nextBraceOrCommaToken.index, [
|
|
|
|
...previousArgs,
|
|
|
|
literal,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
|
|
|
} else if (
|
|
|
|
argumentToken.token.type === 'brace' &&
|
|
|
|
argumentToken.token.value === ')'
|
|
|
|
) {
|
2022-11-20 17:43:21 +11:00
|
|
|
return makeArguments(tokens, argumentToken.index, previousArgs)
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
throw new Error('Expected a previous if statement to match')
|
2022-11-14 13:28:16 +11:00
|
|
|
}
|
|
|
|
|
2022-11-13 11:14:30 +11:00
|
|
|
interface VariableDeclaration extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'VariableDeclaration'
|
|
|
|
declarations: VariableDeclarator[]
|
|
|
|
kind: 'const' | 'unknown' | 'fn' | 'sketch' | 'path' //| "solid" | "surface" | "face"
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeVariableDeclaration(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { declaration: VariableDeclaration; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const declarationStartToken = nextMeaningfulToken(tokens, index)
|
2022-11-13 11:14:30 +11:00
|
|
|
const { declarations, lastIndex } = makeVariableDeclarators(
|
|
|
|
tokens,
|
|
|
|
declarationStartToken.index
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
|
|
|
declaration: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'VariableDeclaration',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: declarations[declarations.length - 1].end,
|
2022-11-17 20:17:00 +11:00
|
|
|
kind:
|
2022-11-26 08:34:23 +11:00
|
|
|
currentToken.value === 'const'
|
|
|
|
? 'const'
|
|
|
|
: currentToken.value === 'fn'
|
|
|
|
? 'fn'
|
|
|
|
: currentToken.value === 'sketch'
|
|
|
|
? 'sketch'
|
|
|
|
: currentToken.value === 'path'
|
|
|
|
? 'path'
|
|
|
|
: 'unknown',
|
2022-11-13 11:14:30 +11:00
|
|
|
declarations,
|
|
|
|
},
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:03:09 +11:00
|
|
|
export type Value =
|
2022-11-18 08:20:18 +11:00
|
|
|
| Literal
|
|
|
|
| Identifier
|
|
|
|
| BinaryExpression
|
|
|
|
| FunctionExpression
|
2022-11-20 17:43:21 +11:00
|
|
|
| CallExpression
|
2022-11-26 08:34:23 +11:00
|
|
|
| SketchExpression
|
2022-11-18 08:20:18 +11:00
|
|
|
|
|
|
|
function makeValue(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { value: Value; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const { token: nextToken } = nextMeaningfulToken(tokens, index)
|
|
|
|
if (nextToken.type === 'brace' && nextToken.value === '(') {
|
|
|
|
const { expression, lastIndex } = makeCallExpression(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
value: expression,
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (currentToken.type === 'word' && nextToken.type === 'operator') {
|
|
|
|
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
value: expression,
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (currentToken.type === 'word') {
|
|
|
|
const identifier = makeIdentifier(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
value: identifier,
|
|
|
|
lastIndex: index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (currentToken.type === 'number' || currentToken.type === 'string') {
|
|
|
|
const literal = makeLiteral(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
value: literal,
|
|
|
|
lastIndex: index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
throw new Error('Expected a previous if statement to match')
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
|
|
|
|
2022-11-13 11:14:30 +11:00
|
|
|
interface VariableDeclarator extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'VariableDeclarator'
|
|
|
|
id: Identifier
|
|
|
|
init: Value
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeVariableDeclarators(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
|
|
|
previousDeclarators: VariableDeclarator[] = []
|
|
|
|
): {
|
2022-11-26 08:34:23 +11:00
|
|
|
declarations: VariableDeclarator[]
|
|
|
|
lastIndex: number
|
2022-11-13 11:14:30 +11:00
|
|
|
} {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const assignmentToken = nextMeaningfulToken(tokens, index)
|
|
|
|
const declarationToken = previousMeaningfulToken(tokens, index)
|
|
|
|
const contentsStartToken = nextMeaningfulToken(tokens, assignmentToken.index)
|
|
|
|
const nextAfterInit = nextMeaningfulToken(tokens, contentsStartToken.index)
|
|
|
|
let init: Value
|
|
|
|
let lastIndex = contentsStartToken.index
|
2022-11-17 20:17:00 +11:00
|
|
|
if (
|
2022-11-26 08:34:23 +11:00
|
|
|
contentsStartToken.token.type === 'brace' &&
|
|
|
|
contentsStartToken.token.value === '('
|
2022-11-17 20:17:00 +11:00
|
|
|
) {
|
2022-11-26 08:34:23 +11:00
|
|
|
const closingBraceIndex = findClosingBrace(tokens, contentsStartToken.index)
|
|
|
|
const arrowToken = nextMeaningfulToken(tokens, closingBraceIndex)
|
2022-11-17 20:17:00 +11:00
|
|
|
if (
|
2022-11-26 08:34:23 +11:00
|
|
|
arrowToken.token.type === 'operator' &&
|
|
|
|
arrowToken.token.value === '=>'
|
2022-11-17 20:17:00 +11:00
|
|
|
) {
|
|
|
|
const { expression, lastIndex: arrowFunctionLastIndex } =
|
2022-11-26 08:34:23 +11:00
|
|
|
makeFunctionExpression(tokens, contentsStartToken.index)
|
|
|
|
init = expression
|
|
|
|
lastIndex = arrowFunctionLastIndex
|
2022-11-17 20:17:00 +11:00
|
|
|
} else {
|
2022-11-26 08:34:23 +11:00
|
|
|
throw new Error('TODO - handle expression with braces')
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-20 17:43:21 +11:00
|
|
|
} else if (
|
2022-11-26 08:34:23 +11:00
|
|
|
declarationToken.token.type === 'word' &&
|
|
|
|
declarationToken.token.value === 'sketch'
|
2022-11-20 17:43:21 +11:00
|
|
|
) {
|
2022-11-26 08:34:23 +11:00
|
|
|
const sketchExp = makeSketchExpression(tokens, assignmentToken.index)
|
|
|
|
init = sketchExp.expression
|
|
|
|
lastIndex = sketchExp.lastIndex
|
|
|
|
} else if (nextAfterInit.token?.type === 'operator') {
|
|
|
|
const binExp = makeBinaryExpression(tokens, contentsStartToken.index)
|
|
|
|
init = binExp.expression
|
|
|
|
lastIndex = binExp.lastIndex
|
2022-11-20 17:43:21 +11:00
|
|
|
} else if (
|
2022-11-26 08:34:23 +11:00
|
|
|
nextAfterInit.token?.type === 'brace' &&
|
|
|
|
nextAfterInit.token.value === '('
|
2022-11-20 17:43:21 +11:00
|
|
|
) {
|
2022-11-26 08:34:23 +11:00
|
|
|
const callExInfo = makeCallExpression(tokens, contentsStartToken.index)
|
|
|
|
init = callExInfo.expression
|
|
|
|
lastIndex = callExInfo.lastIndex
|
2022-11-13 11:14:30 +11:00
|
|
|
} else {
|
2022-11-26 08:34:23 +11:00
|
|
|
init = makeLiteral(tokens, contentsStartToken.index)
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
const currentDeclarator: VariableDeclarator = {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'VariableDeclarator',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: tokens[lastIndex].end,
|
|
|
|
id: makeIdentifier(tokens, index),
|
|
|
|
init,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
|
|
|
declarations: [...previousDeclarators, currentDeclarator],
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 08:34:23 +11:00
|
|
|
export type BinaryPart = Literal | Identifier
|
2022-11-13 11:14:30 +11:00
|
|
|
// | BinaryExpression
|
|
|
|
// | CallExpression
|
|
|
|
// | MemberExpression
|
|
|
|
// | ArrayExpression
|
|
|
|
// | ObjectExpression
|
|
|
|
// | UnaryExpression
|
|
|
|
// | LogicalExpression
|
|
|
|
// | ConditionalExpression
|
|
|
|
|
2022-11-26 19:03:09 +11:00
|
|
|
export interface Literal extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'Literal'
|
|
|
|
value: string | number | boolean | null
|
|
|
|
raw: string
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
interface Identifier extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'Identifier'
|
|
|
|
name: string
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeIdentifier(token: Token[], index: number): Identifier {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = token[index]
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'Identifier',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: currentToken.end,
|
|
|
|
name: currentToken.value,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeLiteral(tokens: Token[], index: number): Literal {
|
2022-11-26 08:34:23 +11:00
|
|
|
const token = tokens[index]
|
2022-11-14 13:28:16 +11:00
|
|
|
const value =
|
2022-11-26 08:34:23 +11:00
|
|
|
token.type === 'number' ? Number(token.value) : token.value.slice(1, -1)
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'Literal',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: token.start,
|
|
|
|
end: token.end,
|
|
|
|
value,
|
|
|
|
raw: token.value,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-20 09:41:21 +11:00
|
|
|
export interface BinaryExpression extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'BinaryExpression'
|
|
|
|
operator: string
|
|
|
|
left: BinaryPart
|
|
|
|
right: BinaryPart
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-18 08:20:18 +11:00
|
|
|
function makeBinaryPart(
|
2022-11-13 11:14:30 +11:00
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
2022-11-18 08:20:18 +11:00
|
|
|
): { part: BinaryPart; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
if (currentToken.type === 'word') {
|
|
|
|
const identifier = makeIdentifier(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
part: identifier,
|
|
|
|
lastIndex: index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (currentToken.type === 'number' || currentToken.type === 'string') {
|
|
|
|
const literal = makeLiteral(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
part: literal,
|
|
|
|
lastIndex: index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
throw new Error('Expected a previous if statement to match')
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeBinaryExpression(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { expression: BinaryExpression; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const { part: left } = makeBinaryPart(tokens, index)
|
2022-11-13 11:14:30 +11:00
|
|
|
const { token: operatorToken, index: operatorIndex } = nextMeaningfulToken(
|
|
|
|
tokens,
|
|
|
|
index
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
|
|
|
const rightToken = nextMeaningfulToken(tokens, operatorIndex)
|
|
|
|
const { part: right } = makeBinaryPart(tokens, rightToken.index)
|
2022-11-13 11:14:30 +11:00
|
|
|
return {
|
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'BinaryExpression',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: right.end,
|
|
|
|
left,
|
|
|
|
operator: operatorToken.value,
|
|
|
|
right,
|
|
|
|
},
|
|
|
|
lastIndex: rightToken.index,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:03:09 +11:00
|
|
|
export interface SketchExpression extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'SketchExpression'
|
|
|
|
body: BlockStatement
|
2022-11-20 17:43:21 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeSketchExpression(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { expression: SketchExpression; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const { block, lastIndex: bodyLastIndex } = makeBlockStatement(tokens, index)
|
|
|
|
const endToken = tokens[bodyLastIndex]
|
2022-11-20 17:43:21 +11:00
|
|
|
|
|
|
|
return {
|
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'SketchExpression',
|
2022-11-20 17:43:21 +11:00
|
|
|
start: currentToken.start,
|
2022-11-26 08:34:23 +11:00
|
|
|
end: endToken.end,
|
2022-11-20 17:43:21 +11:00
|
|
|
body: block,
|
|
|
|
},
|
|
|
|
lastIndex: bodyLastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-20 17:43:21 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 19:03:09 +11:00
|
|
|
export interface FunctionExpression extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'FunctionExpression'
|
|
|
|
id: Identifier | null
|
|
|
|
params: Identifier[]
|
|
|
|
body: BlockStatement
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeFunctionExpression(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { expression: FunctionExpression; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const closingBraceIndex = findClosingBrace(tokens, index)
|
|
|
|
const arrowToken = nextMeaningfulToken(tokens, closingBraceIndex)
|
|
|
|
const bodyStartToken = nextMeaningfulToken(tokens, arrowToken.index)
|
|
|
|
const { params } = makeParams(tokens, index)
|
2022-11-17 20:17:00 +11:00
|
|
|
const { block, lastIndex: bodyLastIndex } = makeBlockStatement(
|
|
|
|
tokens,
|
|
|
|
bodyStartToken.index
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-17 20:17:00 +11:00
|
|
|
return {
|
|
|
|
expression: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'FunctionExpression',
|
2022-11-17 20:17:00 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: tokens[bodyLastIndex].end,
|
|
|
|
id: null,
|
|
|
|
params,
|
|
|
|
body: block,
|
|
|
|
},
|
|
|
|
lastIndex: bodyLastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
|
|
|
|
2022-11-18 08:20:18 +11:00
|
|
|
function makeParams(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
|
|
|
previousParams: Identifier[] = []
|
|
|
|
): { params: Identifier[]; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const braceOrCommaToken = tokens[index]
|
|
|
|
const argumentToken = nextMeaningfulToken(tokens, index)
|
2022-11-18 08:20:18 +11:00
|
|
|
const shouldFinishRecursion =
|
2022-11-26 08:34:23 +11:00
|
|
|
(argumentToken.token.type === 'brace' &&
|
|
|
|
argumentToken.token.value === ')') ||
|
|
|
|
(braceOrCommaToken.type === 'brace' && braceOrCommaToken.value === ')')
|
2022-11-18 08:20:18 +11:00
|
|
|
if (shouldFinishRecursion) {
|
2022-11-26 08:34:23 +11:00
|
|
|
return { params: previousParams, lastIndex: index }
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
|
|
|
|
const identifier = makeIdentifier(tokens, argumentToken.index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return makeParams(tokens, nextBraceOrCommaToken.index, [
|
|
|
|
...previousParams,
|
|
|
|
identifier,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
|
|
|
|
2022-11-17 20:17:00 +11:00
|
|
|
interface BlockStatement extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'BlockStatement'
|
|
|
|
body: Body[]
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeBlockStatement(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { block: BlockStatement; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const openingCurly = tokens[index]
|
|
|
|
const nextToken = nextMeaningfulToken(tokens, index)
|
2022-11-17 20:17:00 +11:00
|
|
|
const { body, lastIndex } =
|
2022-11-26 08:34:23 +11:00
|
|
|
nextToken.token.value === '}'
|
2022-11-17 20:17:00 +11:00
|
|
|
? { body: [], lastIndex: nextToken.index }
|
2022-11-26 08:34:23 +11:00
|
|
|
: makeBody({ tokens, tokenIndex: nextToken.index })
|
2022-11-17 20:17:00 +11:00
|
|
|
return {
|
|
|
|
block: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'BlockStatement',
|
2022-11-17 20:17:00 +11:00
|
|
|
start: openingCurly.start,
|
2022-11-28 09:37:46 +11:00
|
|
|
end: tokens[lastIndex]?.end || 0,
|
2022-11-17 20:17:00 +11:00
|
|
|
body,
|
|
|
|
},
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
|
|
|
|
2022-11-18 08:20:18 +11:00
|
|
|
interface ReturnStatement extends GeneralStatement {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'ReturnStatement'
|
|
|
|
argument: Value
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeReturnStatement(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number
|
|
|
|
): { statement: ReturnStatement; lastIndex: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const currentToken = tokens[index]
|
|
|
|
const nextToken = nextMeaningfulToken(tokens, index)
|
|
|
|
const { value, lastIndex } = makeValue(tokens, nextToken.index)
|
2022-11-18 08:20:18 +11:00
|
|
|
return {
|
|
|
|
statement: {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'ReturnStatement',
|
2022-11-18 08:20:18 +11:00
|
|
|
start: currentToken.start,
|
|
|
|
end: tokens[lastIndex].end,
|
|
|
|
argument: value,
|
|
|
|
},
|
|
|
|
lastIndex,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 08:34:23 +11:00
|
|
|
export type All = Program | ExpressionStatement[] | BinaryExpression | Literal
|
2022-11-13 11:14:30 +11:00
|
|
|
|
|
|
|
function nextMeaningfulToken(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
|
|
|
offset: number = 1
|
|
|
|
): { token: Token; index: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const newIndex = index + offset
|
|
|
|
const token = tokens[newIndex]
|
2022-11-13 11:14:30 +11:00
|
|
|
if (!token) {
|
2022-11-26 08:34:23 +11:00
|
|
|
return { token, index: tokens.length }
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (token.type === 'whitespace') {
|
|
|
|
return nextMeaningfulToken(tokens, index, offset + 1)
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
return { token, index: newIndex }
|
2022-11-13 11:14:30 +11:00
|
|
|
}
|
|
|
|
|
2022-11-20 17:43:21 +11:00
|
|
|
function previousMeaningfulToken(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
|
|
|
offset: number = 1
|
|
|
|
): { token: Token; index: number } {
|
2022-11-26 08:34:23 +11:00
|
|
|
const newIndex = index - offset
|
|
|
|
const token = tokens[newIndex]
|
2022-11-20 17:43:21 +11:00
|
|
|
if (!token) {
|
2022-11-26 08:34:23 +11:00
|
|
|
return { token, index: 0 }
|
2022-11-20 17:43:21 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (token.type === 'whitespace') {
|
|
|
|
return previousMeaningfulToken(tokens, index, offset + 1)
|
2022-11-20 17:43:21 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
return { token, index: newIndex }
|
2022-11-20 17:43:21 +11:00
|
|
|
}
|
|
|
|
|
2022-11-26 08:34:23 +11:00
|
|
|
type Body = ExpressionStatement | VariableDeclaration | ReturnStatement
|
2022-11-13 11:14:30 +11:00
|
|
|
|
2022-11-17 20:17:00 +11:00
|
|
|
function makeBody(
|
2022-11-20 17:43:21 +11:00
|
|
|
{
|
|
|
|
tokens,
|
|
|
|
tokenIndex = 0,
|
|
|
|
}: {
|
2022-11-26 08:34:23 +11:00
|
|
|
tokens: Token[]
|
|
|
|
tokenIndex?: number
|
2022-11-20 17:43:21 +11:00
|
|
|
},
|
2022-11-17 20:17:00 +11:00
|
|
|
previousBody: Body[] = []
|
|
|
|
): { body: Body[]; lastIndex: number } {
|
|
|
|
if (tokenIndex >= tokens.length) {
|
2022-11-26 08:34:23 +11:00
|
|
|
return { body: previousBody, lastIndex: tokenIndex }
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-18 08:20:18 +11:00
|
|
|
|
2022-11-26 08:34:23 +11:00
|
|
|
const token = tokens[tokenIndex]
|
|
|
|
if (token.type === 'brace' && token.value === '}') {
|
|
|
|
return { body: previousBody, lastIndex: tokenIndex }
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (typeof token === 'undefined') {
|
|
|
|
console.log('probably should throw')
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (token.type === 'whitespace') {
|
|
|
|
return makeBody({ tokens, tokenIndex: tokenIndex + 1 }, previousBody)
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
const nextToken = nextMeaningfulToken(tokens, tokenIndex)
|
2022-11-17 20:17:00 +11:00
|
|
|
if (
|
2022-11-26 08:34:23 +11:00
|
|
|
token.type === 'word' &&
|
|
|
|
(token.value === 'const' ||
|
|
|
|
token.value === 'fn' ||
|
|
|
|
token.value === 'sketch' ||
|
|
|
|
token.value === 'path')
|
2022-11-17 20:17:00 +11:00
|
|
|
) {
|
|
|
|
const { declaration, lastIndex } = makeVariableDeclaration(
|
|
|
|
tokens,
|
|
|
|
tokenIndex
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
|
|
|
const nextThing = nextMeaningfulToken(tokens, lastIndex)
|
2022-11-20 17:43:21 +11:00
|
|
|
return makeBody({ tokens, tokenIndex: nextThing.index }, [
|
|
|
|
...previousBody,
|
|
|
|
declaration,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
if (token.type === 'word' && token.value === 'return') {
|
|
|
|
const { statement, lastIndex } = makeReturnStatement(tokens, tokenIndex)
|
|
|
|
const nextThing = nextMeaningfulToken(tokens, lastIndex)
|
2022-11-20 17:43:21 +11:00
|
|
|
return makeBody({ tokens, tokenIndex: nextThing.index }, [
|
|
|
|
...previousBody,
|
|
|
|
statement,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
2022-11-18 08:20:18 +11:00
|
|
|
}
|
2022-11-20 17:43:21 +11:00
|
|
|
if (
|
2022-11-26 08:34:23 +11:00
|
|
|
token.type === 'word' &&
|
|
|
|
nextToken.token.type === 'brace' &&
|
|
|
|
nextToken.token.value === '('
|
2022-11-20 17:43:21 +11:00
|
|
|
) {
|
2022-11-17 20:17:00 +11:00
|
|
|
const { expression, lastIndex } = makeExpressionStatement(
|
|
|
|
tokens,
|
|
|
|
tokenIndex
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
|
|
|
const nextThing = nextMeaningfulToken(tokens, lastIndex)
|
2022-11-20 17:43:21 +11:00
|
|
|
return makeBody({ tokens, tokenIndex: nextThing.index }, [
|
|
|
|
...previousBody,
|
|
|
|
expression,
|
2022-11-26 08:34:23 +11:00
|
|
|
])
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
|
|
|
if (
|
2022-11-26 08:34:23 +11:00
|
|
|
(token.type === 'number' || token.type === 'word') &&
|
|
|
|
nextMeaningfulToken(tokens, tokenIndex).token.type === 'operator'
|
2022-11-17 20:17:00 +11:00
|
|
|
) {
|
|
|
|
const { expression, lastIndex } = makeExpressionStatement(
|
|
|
|
tokens,
|
|
|
|
tokenIndex
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-17 20:17:00 +11:00
|
|
|
// return startTree(tokens, tokenIndex, [...previousBody, makeExpressionStatement(tokens, tokenIndex)]);
|
2022-11-26 08:34:23 +11:00
|
|
|
return { body: [...previousBody, expression], lastIndex }
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-26 08:34:23 +11:00
|
|
|
console.log('should throw', tokens.slice(tokenIndex))
|
|
|
|
throw new Error('Unexpected token')
|
2022-11-17 20:17:00 +11:00
|
|
|
}
|
2022-11-13 11:14:30 +11:00
|
|
|
export const abstractSyntaxTree = (tokens: Token[]): Program => {
|
2022-11-26 08:34:23 +11:00
|
|
|
const { body } = makeBody({ tokens })
|
2022-11-13 11:14:30 +11:00
|
|
|
const program: Program = {
|
2022-11-26 08:34:23 +11:00
|
|
|
type: 'Program',
|
2022-11-13 11:14:30 +11:00
|
|
|
start: 0,
|
|
|
|
end: body[body.length - 1].end,
|
|
|
|
body: body,
|
2022-11-26 08:34:23 +11:00
|
|
|
}
|
|
|
|
return program
|
|
|
|
}
|
2022-11-17 16:06:38 +11:00
|
|
|
|
|
|
|
export function findClosingBrace(
|
|
|
|
tokens: Token[],
|
|
|
|
index: number,
|
|
|
|
_braceCount: number = 0,
|
2022-11-26 08:34:23 +11:00
|
|
|
_searchOpeningBrace: string = ''
|
2022-11-17 16:06:38 +11:00
|
|
|
): number {
|
|
|
|
const closingBraceMap: { [key: string]: string } = {
|
2022-11-26 08:34:23 +11:00
|
|
|
'(': ')',
|
|
|
|
'{': '}',
|
|
|
|
'[': ']',
|
|
|
|
}
|
|
|
|
const currentToken = tokens[index]
|
|
|
|
let searchOpeningBrace = _searchOpeningBrace
|
|
|
|
|
|
|
|
const isFirstCall = !searchOpeningBrace && _braceCount === 0
|
2022-11-17 16:06:38 +11:00
|
|
|
if (isFirstCall) {
|
2022-11-26 08:34:23 +11:00
|
|
|
searchOpeningBrace = currentToken.value
|
|
|
|
if (!['(', '{', '['].includes(searchOpeningBrace)) {
|
2022-11-17 16:06:38 +11:00
|
|
|
throw new Error(
|
|
|
|
`expected to be started on a opening brace ( { [, instead found '${searchOpeningBrace}'`
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-17 16:06:38 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const foundClosingBrace =
|
|
|
|
_braceCount === 1 &&
|
2022-11-26 08:34:23 +11:00
|
|
|
currentToken.value === closingBraceMap[searchOpeningBrace]
|
|
|
|
const foundAnotherOpeningBrace = currentToken.value === searchOpeningBrace
|
2022-11-17 16:06:38 +11:00
|
|
|
const foundAnotherClosingBrace =
|
2022-11-26 08:34:23 +11:00
|
|
|
currentToken.value === closingBraceMap[searchOpeningBrace]
|
2022-11-17 16:06:38 +11:00
|
|
|
|
|
|
|
if (foundClosingBrace) {
|
2022-11-26 08:34:23 +11:00
|
|
|
return index
|
2022-11-17 16:06:38 +11:00
|
|
|
}
|
|
|
|
if (foundAnotherOpeningBrace) {
|
|
|
|
return findClosingBrace(
|
|
|
|
tokens,
|
|
|
|
index + 1,
|
|
|
|
_braceCount + 1,
|
|
|
|
searchOpeningBrace
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-17 16:06:38 +11:00
|
|
|
}
|
|
|
|
if (foundAnotherClosingBrace) {
|
|
|
|
return findClosingBrace(
|
|
|
|
tokens,
|
|
|
|
index + 1,
|
|
|
|
_braceCount - 1,
|
|
|
|
searchOpeningBrace
|
2022-11-26 08:34:23 +11:00
|
|
|
)
|
2022-11-17 16:06:38 +11:00
|
|
|
}
|
|
|
|
// non-brace token, increment and continue
|
2022-11-26 08:34:23 +11:00
|
|
|
return findClosingBrace(tokens, index + 1, _braceCount, searchOpeningBrace)
|
2022-11-17 16:06:38 +11:00
|
|
|
}
|
2022-11-28 09:37:46 +11:00
|
|
|
|
|
|
|
export function addSketchTo(
|
|
|
|
node: Program,
|
|
|
|
name = ''
|
|
|
|
): { modifiedAst: Program; id: string } {
|
|
|
|
const _node = { ...node }
|
|
|
|
const dumbyStartend = { start: 0, end: 0 }
|
|
|
|
const _name = name || findUniqueName(node, 'mySketch')
|
|
|
|
const sketchBody: BlockStatement = {
|
|
|
|
type: 'BlockStatement',
|
|
|
|
...dumbyStartend,
|
|
|
|
body: [],
|
|
|
|
}
|
|
|
|
const sketch: SketchExpression = {
|
|
|
|
type: 'SketchExpression',
|
|
|
|
...dumbyStartend,
|
|
|
|
body: sketchBody,
|
|
|
|
}
|
|
|
|
const sketchVariableDeclaration: VariableDeclaration = {
|
|
|
|
type: 'VariableDeclaration',
|
|
|
|
...dumbyStartend,
|
|
|
|
kind: 'sketch',
|
|
|
|
declarations: [
|
|
|
|
{
|
|
|
|
type: 'VariableDeclarator',
|
|
|
|
...dumbyStartend,
|
|
|
|
id: {
|
|
|
|
type: 'Identifier',
|
|
|
|
...dumbyStartend,
|
|
|
|
name: _name,
|
|
|
|
},
|
|
|
|
init: sketch,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
const showCallIndex = getShowIndex(_node)
|
|
|
|
if (showCallIndex === -1) {
|
|
|
|
_node.body = [...node.body, sketchVariableDeclaration]
|
|
|
|
} else {
|
|
|
|
const newBody = [...node.body]
|
|
|
|
newBody.splice(showCallIndex, 0, sketchVariableDeclaration)
|
|
|
|
_node.body = newBody
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
modifiedAst: addToShow(_node, _name),
|
|
|
|
id: _name,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function findUniqueName(
|
|
|
|
ast: Program | string,
|
|
|
|
name: string,
|
|
|
|
index = 1
|
|
|
|
): string {
|
|
|
|
let searchStr = ''
|
|
|
|
if (typeof ast === 'string') {
|
|
|
|
searchStr = ast
|
|
|
|
} else {
|
|
|
|
searchStr = JSON.stringify(ast)
|
|
|
|
}
|
|
|
|
const indexStr = `${index}`.padStart(3, '0')
|
|
|
|
const newName = `${name}${indexStr}`
|
|
|
|
const isInString = searchStr.includes(newName)
|
|
|
|
if (!isInString) {
|
|
|
|
return newName
|
|
|
|
}
|
|
|
|
return findUniqueName(searchStr, name, 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,
|
|
|
|
id: string,
|
|
|
|
to: [number, number]
|
|
|
|
): { modifiedAst: Program; id: string } {
|
|
|
|
const _node = { ...node }
|
|
|
|
const dumbyStartend = { start: 0, end: 0 }
|
2022-11-28 19:44:08 +11:00
|
|
|
const { index, sketchDeclaration, sketchExpression } = getSketchStatement(
|
|
|
|
_node,
|
|
|
|
id
|
|
|
|
)
|
2022-11-28 09:37:46 +11:00
|
|
|
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]
|
|
|
|
const newSketchExpression: SketchExpression = {
|
|
|
|
...sketchExpression,
|
|
|
|
body: {
|
|
|
|
...sketchExpression.body,
|
|
|
|
body: newBody,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
const newSketchDeclaration: VariableDeclaration = {
|
|
|
|
...sketchDeclaration,
|
|
|
|
declarations: [
|
|
|
|
{
|
|
|
|
...sketchDeclaration.declarations[0],
|
|
|
|
init: newSketchExpression,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
_node.body[index] = newSketchDeclaration
|
|
|
|
return {
|
|
|
|
modifiedAst: _node,
|
|
|
|
id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSketchStatement(
|
|
|
|
node: Program,
|
|
|
|
id: string
|
|
|
|
): {
|
|
|
|
sketchDeclaration: VariableDeclaration
|
|
|
|
sketchExpression: SketchExpression
|
|
|
|
index: number
|
|
|
|
} {
|
|
|
|
const sketchStatementIndex = node.body.findIndex(
|
|
|
|
(statement) =>
|
|
|
|
statement.type === 'VariableDeclaration' &&
|
|
|
|
statement.kind === 'sketch' &&
|
|
|
|
statement.declarations[0].id.type === 'Identifier' &&
|
|
|
|
statement.declarations[0].id.name === id
|
|
|
|
)
|
|
|
|
const sketchStatement = node.body.find(
|
|
|
|
(statement) =>
|
|
|
|
statement.type === 'VariableDeclaration' &&
|
|
|
|
statement.kind === 'sketch' &&
|
|
|
|
statement.declarations[0].id.type === 'Identifier' &&
|
|
|
|
statement.declarations[0].id.name === id
|
|
|
|
)
|
|
|
|
if (
|
|
|
|
!sketchStatement ||
|
|
|
|
sketchStatement.type !== 'VariableDeclaration' ||
|
|
|
|
sketchStatement.declarations[0].init.type !== 'SketchExpression'
|
|
|
|
)
|
|
|
|
throw new Error('No sketch found')
|
|
|
|
|
|
|
|
return {
|
|
|
|
sketchDeclaration: sketchStatement,
|
|
|
|
sketchExpression: sketchStatement.declarations[0].init,
|
|
|
|
index: sketchStatementIndex,
|
|
|
|
}
|
|
|
|
}
|