add object declarations
This commit is contained in:
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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') {
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
Reference in New Issue
Block a user