add object declarations

This commit is contained in:
Kurt Hutten IrevDev
2023-01-01 21:48:30 +11:00
parent 84d76b5763
commit dbf8a993e5
8 changed files with 355 additions and 0 deletions

View File

@ -1068,4 +1068,150 @@ describe('testing pipe operator special', () => {
},
])
})
test('object expression ast', () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 15,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 15,
id: {
type: 'Identifier',
start: 6,
end: 11,
name: 'three',
},
init: {
type: 'Literal',
start: 14,
end: 15,
value: 3,
raw: '3',
},
},
],
},
{
type: 'VariableDeclaration',
start: 16,
end: 83,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 22,
end: 83,
id: {
type: 'Identifier',
start: 22,
end: 24,
name: 'yo',
},
init: {
type: 'ObjectExpression',
start: 27,
end: 83,
properties: [
{
type: 'ObjectProperty',
start: 28,
end: 39,
key: {
type: 'Identifier',
start: 28,
end: 32,
name: 'aStr',
},
value: {
type: 'Literal',
start: 34,
end: 39,
value: 'str',
raw: "'str'",
},
},
{
type: 'ObjectProperty',
start: 41,
end: 48,
key: {
type: 'Identifier',
start: 41,
end: 45,
name: 'anum',
},
value: {
type: 'Literal',
start: 47,
end: 48,
value: 2,
raw: '2',
},
},
{
type: 'ObjectProperty',
start: 50,
end: 67,
key: {
type: 'Identifier',
start: 50,
end: 60,
name: 'identifier',
},
value: {
type: 'Identifier',
start: 62,
end: 67,
name: 'three',
},
},
{
type: 'ObjectProperty',
start: 69,
end: 82,
key: {
type: 'Identifier',
start: 69,
end: 75,
name: 'binExp',
},
value: {
type: 'BinaryExpression',
start: 77,
end: 82,
left: {
type: 'Literal',
start: 77,
end: 78,
value: 4,
raw: '4',
},
operator: '+',
right: {
type: 'Literal',
start: 81,
end: 82,
value: 5,
raw: '5',
},
},
},
],
},
},
],
},
])
})
})

View File

@ -20,6 +20,7 @@ type syntaxType =
| 'MemberExpression'
| 'ArrayExpression'
| 'ObjectExpression'
| 'ObjectProperty'
| 'Property'
| 'LogicalExpression'
| 'ConditionalExpression'
@ -288,6 +289,7 @@ export type Value =
| PipeExpression
| PipeSubstitution
| ArrayExpression
| ObjectExpression
function makeValue(
tokens: Token[],
@ -379,6 +381,16 @@ function makeVariableDeclarators(
} else {
throw new Error('TODO - handle expression with braces')
}
} else if (
contentsStartToken.token.type === 'brace' &&
contentsStartToken.token.value === '{'
) {
const objectExpression = makeObjectExpression(
tokens,
contentsStartToken.index
)
init = objectExpression.expression
lastIndex = objectExpression.lastIndex
} else if (
declarationToken.token.type === 'word' &&
declarationToken.token.value === 'sketch'
@ -531,6 +543,81 @@ function makeArrayExpression(
}
}
export interface ObjectExpression extends GeneralStatement {
type: 'ObjectExpression'
properties: ObjectProperty[]
}
interface ObjectProperty extends GeneralStatement {
type: 'ObjectProperty'
key: Identifier
value: Value
}
function makeObjectExpression(
tokens: Token[],
index: number
): {
expression: ObjectExpression
lastIndex: number
} {
// should be called with the opening brace '{' index
const openingBraceToken = tokens[index]
const firstPropertyToken = nextMeaningfulToken(tokens, index)
const { properties, lastIndex } = makeObjectProperties(
tokens,
firstPropertyToken.index
)
return {
expression: {
type: 'ObjectExpression',
start: openingBraceToken.start,
end: tokens[lastIndex].end,
properties,
},
lastIndex,
}
}
function makeObjectProperties(
tokens: Token[],
index: number,
previousProperties: ObjectProperty[] = []
): { properties: ObjectProperty[]; lastIndex: number } {
// should be called with the key after the opening brace '{'
const propertyKeyToken = tokens[index]
if (propertyKeyToken.type === 'brace' && propertyKeyToken.value === '}') {
return {
properties: previousProperties,
lastIndex: index,
}
}
const colonToken = nextMeaningfulToken(tokens, index)
const valueStartToken = nextMeaningfulToken(tokens, colonToken.index)
const value = makeValue(tokens, valueStartToken.index)
const commaOrClosingBraceToken = nextMeaningfulToken(tokens, value.lastIndex)
let objectProperty: ObjectProperty = {
type: 'ObjectProperty',
start: propertyKeyToken.start,
end: value.value.end,
key: makeIdentifier(tokens, index),
value: value.value,
}
const nextKeyToken = nextMeaningfulToken(
tokens,
commaOrClosingBraceToken.index
)
const nextKeyIndex =
commaOrClosingBraceToken.token.type === 'brace' &&
commaOrClosingBraceToken.token.value === '}'
? commaOrClosingBraceToken.index
: nextKeyToken.index
return makeObjectProperties(tokens, nextKeyIndex, [
...previousProperties,
objectProperty,
])
}
export interface BinaryExpression extends GeneralStatement {
type: 'BinaryExpression'
operator: string

View File

@ -190,6 +190,22 @@ show(mySketch)
yo: [1, '2', 3, 9],
})
})
it('execute object expression', () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { root } = exe(code)
expect(root).toEqual({
three: 3,
yo: {
aStr: 'str',
anum: 2,
identifier: 3,
binExp: 9,
},
})
})
})
// helpers

View File

@ -3,6 +3,7 @@ import {
BinaryPart,
BinaryExpression,
PipeExpression,
ObjectExpression,
} from './abstractSyntaxTree'
import { Path, Transform, SketchGeo, sketchFns, ExtrudeGeo } from './sketch'
import { BufferGeometry, Quaternion, Vector3 } from 'three'
@ -61,6 +62,9 @@ export const executor = (
}
}
)
} else if (declaration.init.type === 'ObjectExpression') {
const obj = executeObjectExpression(_programMemory, declaration.init)
_programMemory.root[variableName] = obj
} else if (declaration.init.type === 'SketchExpression') {
const sketchInit = declaration.init
const fnMemory: ProgramMemory = {
@ -382,6 +386,41 @@ function executePipeBody(
throw new Error('Invalid pipe expression')
}
function executeObjectExpression(
_programMemory: ProgramMemory,
objExp: ObjectExpression
) {
const obj: { [key: string]: any } = {}
objExp.properties.forEach((property) => {
if (property.type === 'ObjectProperty') {
if (property.value.type === 'Literal') {
obj[property.key.name] = property.value.value
} else if (property.value.type === 'BinaryExpression') {
obj[property.key.name] = getBinaryExpressionResult(
property.value,
_programMemory
)
} else if (property.value.type === 'PipeExpression') {
obj[property.key.name] = getPipeExpressionResult(
property.value,
_programMemory
)
} else if (property.value.type === 'Identifier') {
obj[property.key.name] = _programMemory.root[property.value.name]
} else {
throw new Error(
`Unexpected property type ${property.value.type} in object expression`
)
}
} else {
throw new Error(
`Unexpected property type ${property.type} in object expression`
)
}
})
return obj
}
type SourceRange = [number, number]
export type ViewerArtifact =

View File

@ -122,6 +122,24 @@ show(mySketch)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
it('recast long object exectution', () => {
const code = `const three = 3
const yo = {
aStr: 'str',
anum: 2,
identifier: three,
binExp: 4 + 5
}`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
it('recast short object exectution', () => {
const code = `const yo = { key: 'val' }`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
})
// helpers

View File

@ -8,6 +8,7 @@ import {
FunctionExpression,
SketchExpression,
ArrayExpression,
ObjectExpression,
} from './abstractSyntaxTree'
export function recast(
@ -22,6 +23,8 @@ export function recast(
return indentation + recastBinaryExpression(statement.expression)
} else if (statement.expression.type === 'ArrayExpression') {
return indentation + recastArrayExpression(statement.expression)
} else if (statement.expression.type === 'ObjectExpression') {
return indentation + recastObjectExpression(statement.expression)
} else if (statement.expression.type === 'CallExpression') {
return indentation + recastCallExpression(statement.expression)
}
@ -74,6 +77,25 @@ ${_indentation}${expression.elements
return flatRecast
}
function recastObjectExpression(
expression: ObjectExpression,
indentation = ''
): string {
const flatRecast = `{ ${expression.properties
.map((prop) => `${prop.key.name}: ${recastValue(prop.value)}`)
.join(', ')} }`
const maxArrayLength = 40
if (flatRecast.length > maxArrayLength) {
const _indentation = indentation + ' '
return `{
${_indentation}${expression.properties
.map((prop) => `${prop.key.name}: ${recastValue(prop.value)}`)
.join(`,\n${_indentation}`)}
}`
}
return flatRecast
}
function recastBinaryPart(part: BinaryPart): string {
if (part.type === 'Literal') {
return recastLiteral(part)
@ -106,6 +128,8 @@ function recastArgument(argument: Value): string {
return recastBinaryExpression(argument)
} else if (argument.type === 'ArrayExpression') {
return recastArrayExpression(argument)
} else if (argument.type === 'ObjectExpression') {
return recastObjectExpression(argument)
} else if (argument.type === 'CallExpression') {
return recastCallExpression(argument)
} else if (argument.type === 'FunctionExpression') {
@ -136,6 +160,8 @@ function recastValue(node: Value, indentation = ''): string {
return recastBinaryExpression(node)
} else if (node.type === 'ArrayExpression') {
return recastArrayExpression(node, indentation)
} else if (node.type === 'ObjectExpression') {
return recastObjectExpression(node, indentation)
} else if (node.type === 'Literal') {
return recastLiteral(node)
} else if (node.type === 'FunctionExpression') {

View File

@ -349,6 +349,23 @@ describe('testing lexer', () => {
"brace ']' from 16 to 17",
])
})
it('testing object declaration', () => {
const result = stringSummaryLexer(`const yo = {key: 'value'}`)
expect(result).toEqual([
"word 'const' from 0 to 5",
"whitespace ' ' from 5 to 6",
"word 'yo' from 6 to 8",
"whitespace ' ' from 8 to 9",
"operator '=' from 9 to 10",
"whitespace ' ' from 10 to 11",
"brace '{' from 11 to 12",
"word 'key' from 12 to 15",
"colon ':' from 15 to 16",
"whitespace ' ' from 16 to 17",
"string ''value'' from 17 to 24",
"brace '}' from 24 to 25",
])
})
})
// helpers

View File

@ -15,6 +15,7 @@ const PARAN_END = /^\)/
const ARRAY_START = /^\[/
const ARRAY_END = /^\]/
const COMMA = /^,/
const COLON = /^:/
export const isNumber = (character: string) => NUMBER.test(character)
export const isWhitespace = (character: string) => WHITESPACE.test(character)
@ -28,6 +29,7 @@ export const isParanEnd = (character: string) => PARAN_END.test(character)
export const isArrayStart = (character: string) => ARRAY_START.test(character)
export const isArrayEnd = (character: string) => ARRAY_END.test(character)
export const isComma = (character: string) => COMMA.test(character)
export const isColon = (character: string) => COLON.test(character)
function matchFirst(str: string, regex: RegExp) {
const theMatch = str.match(regex)
@ -46,6 +48,7 @@ export interface Token {
| 'brace'
| 'whitespace'
| 'comma'
| 'colon'
value: string
start: number
end: number
@ -97,6 +100,9 @@ const returnTokenAtIndex = (str: string, startIndex: number): Token | null => {
if (isWord(strFromIndex)) {
return makeToken('word', matchFirst(strFromIndex, WORD), startIndex)
}
if (isColon(strFromIndex)) {
return makeToken('colon', matchFirst(strFromIndex, COLON), startIndex)
}
if (isWhitespace(strFromIndex)) {
return makeToken(
'whitespace',