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

1006 lines
26 KiB
TypeScript
Raw Normal View History

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,
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 =
| Literal
| Identifier
| BinaryExpression
| FunctionExpression
2022-11-20 17:43:21 +11:00
| CallExpression
2022-11-26 08:34:23 +11:00
| SketchExpression
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)
return {
value: expression,
lastIndex,
2022-11-26 08:34:23 +11:00
}
}
2022-11-26 08:34:23 +11:00
if (currentToken.type === 'word' && nextToken.type === 'operator') {
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
return {
value: expression,
lastIndex,
2022-11-26 08:34:23 +11:00
}
}
2022-11-26 08:34:23 +11:00
if (currentToken.type === 'word') {
const identifier = makeIdentifier(tokens, index)
return {
value: identifier,
lastIndex: index,
2022-11-26 08:34:23 +11:00
}
}
2022-11-26 08:34:23 +11:00
if (currentToken.type === 'number' || currentToken.type === 'string') {
const literal = makeLiteral(tokens, index)
return {
value: literal,
lastIndex: index,
2022-11-26 08:34:23 +11:00
}
}
2022-11-26 08:34:23 +11:00
throw new Error('Expected a previous if statement to match')
}
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
}
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
}
function makeBinaryPart(
2022-11-13 11:14:30 +11:00
tokens: Token[],
index: number
): { 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)
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)
return {
part: literal,
lastIndex: index,
2022-11-26 08:34:23 +11:00
}
}
2022-11-26 08:34:23 +11:00
throw new Error('Expected a previous if statement to match')
}
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
}
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)
const shouldFinishRecursion =
2022-11-26 08:34:23 +11:00
(argumentToken.token.type === 'brace' &&
argumentToken.token.value === ')') ||
(braceOrCommaToken.type === 'brace' && braceOrCommaToken.value === ')')
if (shouldFinishRecursion) {
2022-11-26 08:34:23 +11:00
return { params: previousParams, lastIndex: index }
}
2022-11-26 08:34:23 +11:00
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
const identifier = makeIdentifier(tokens, argumentToken.index)
return makeParams(tokens, nextBraceOrCommaToken.index, [
...previousParams,
identifier,
2022-11-26 08:34:23 +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,
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
}
interface ReturnStatement extends GeneralStatement {
2022-11-26 08:34:23 +11:00
type: 'ReturnStatement'
argument: Value
}
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)
return {
statement: {
2022-11-26 08:34:23 +11:00
type: 'ReturnStatement',
start: currentToken.start,
end: tokens[lastIndex].end,
argument: value,
},
lastIndex,
2022-11-26 08:34:23 +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-26 08:34:23 +11:00
const token = tokens[tokenIndex]
if (token.type === 'brace' && token.value === '}') {
return { body: previousBody, lastIndex: tokenIndex }
}
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-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
}
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
)
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,
}
}