add array declarations
This commit is contained in:
@ -995,4 +995,77 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
test('array expression', () => {
|
||||||
|
let code = `const yo = [1, '2', three, 4 + 5]`
|
||||||
|
const tokens = lexer(code)
|
||||||
|
const { body } = abstractSyntaxTree(tokens)
|
||||||
|
expect(body).toEqual([
|
||||||
|
{
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
start: 0,
|
||||||
|
end: 33,
|
||||||
|
kind: 'const',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
start: 6,
|
||||||
|
end: 33,
|
||||||
|
id: {
|
||||||
|
type: 'Identifier',
|
||||||
|
start: 6,
|
||||||
|
end: 8,
|
||||||
|
name: 'yo',
|
||||||
|
},
|
||||||
|
init: {
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
start: 11,
|
||||||
|
end: 33,
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'Literal',
|
||||||
|
start: 12,
|
||||||
|
end: 13,
|
||||||
|
value: 1,
|
||||||
|
raw: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Literal',
|
||||||
|
start: 15,
|
||||||
|
end: 18,
|
||||||
|
value: '2',
|
||||||
|
raw: "'2'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
start: 20,
|
||||||
|
end: 25,
|
||||||
|
name: 'three',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
start: 27,
|
||||||
|
end: 32,
|
||||||
|
left: {
|
||||||
|
type: 'Literal',
|
||||||
|
start: 27,
|
||||||
|
end: 28,
|
||||||
|
value: 4,
|
||||||
|
raw: '4',
|
||||||
|
},
|
||||||
|
operator: '+',
|
||||||
|
right: {
|
||||||
|
type: 'Literal',
|
||||||
|
start: 31,
|
||||||
|
end: 32,
|
||||||
|
value: 5,
|
||||||
|
raw: '5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -270,6 +270,7 @@ export type Value =
|
|||||||
| SketchExpression
|
| SketchExpression
|
||||||
| PipeExpression
|
| PipeExpression
|
||||||
| PipeSubstitution
|
| PipeSubstitution
|
||||||
|
| ArrayExpression
|
||||||
|
|
||||||
function makeValue(
|
function makeValue(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
@ -379,6 +380,16 @@ function makeVariableDeclarators(
|
|||||||
const callExInfo = makeCallExpression(tokens, contentsStartToken.index)
|
const callExInfo = makeCallExpression(tokens, contentsStartToken.index)
|
||||||
init = callExInfo.expression
|
init = callExInfo.expression
|
||||||
lastIndex = callExInfo.lastIndex
|
lastIndex = callExInfo.lastIndex
|
||||||
|
} else if (
|
||||||
|
contentsStartToken.token.type === 'brace' &&
|
||||||
|
contentsStartToken.token.value === '['
|
||||||
|
) {
|
||||||
|
const arrayExpression = makeArrayExpression(
|
||||||
|
tokens,
|
||||||
|
contentsStartToken.index
|
||||||
|
)
|
||||||
|
init = arrayExpression.expression
|
||||||
|
lastIndex = arrayExpression.lastIndex
|
||||||
} else {
|
} else {
|
||||||
init = makeLiteral(tokens, contentsStartToken.index)
|
init = makeLiteral(tokens, contentsStartToken.index)
|
||||||
}
|
}
|
||||||
@ -443,6 +454,66 @@ function makeLiteral(tokens: Token[], index: number): Literal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ArrayExpression extends GeneralStatement {
|
||||||
|
type: 'ArrayExpression'
|
||||||
|
elements: Value[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeArrayElements(
|
||||||
|
tokens: Token[],
|
||||||
|
index: number,
|
||||||
|
previousElements: Value[] = []
|
||||||
|
): { elements: ArrayExpression['elements']; lastIndex: number } {
|
||||||
|
// should be called with the first token after the opening brace
|
||||||
|
const firstElementToken = tokens[index]
|
||||||
|
if (firstElementToken.type === 'brace' && firstElementToken.value === ']') {
|
||||||
|
return {
|
||||||
|
elements: previousElements,
|
||||||
|
lastIndex: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const currentElement = makeValue(tokens, index)
|
||||||
|
const nextToken = nextMeaningfulToken(tokens, currentElement.lastIndex)
|
||||||
|
const isClosingBrace =
|
||||||
|
nextToken.token.type === 'brace' && nextToken.token.value === ']'
|
||||||
|
const isComma = nextToken.token.type === 'comma'
|
||||||
|
if (!isClosingBrace && !isComma) {
|
||||||
|
throw new Error('Expected a comma or closing brace')
|
||||||
|
}
|
||||||
|
const nextCallIndex = isClosingBrace
|
||||||
|
? nextToken.index
|
||||||
|
: nextMeaningfulToken(tokens, nextToken.index).index
|
||||||
|
return makeArrayElements(tokens, nextCallIndex, [
|
||||||
|
...previousElements,
|
||||||
|
currentElement.value,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeArrayExpression(
|
||||||
|
tokens: Token[],
|
||||||
|
index: number
|
||||||
|
): {
|
||||||
|
expression: ArrayExpression
|
||||||
|
lastIndex: number
|
||||||
|
} {
|
||||||
|
// should be called array opening brace '[' index
|
||||||
|
const openingBraceToken = tokens[index]
|
||||||
|
const firstElementToken = nextMeaningfulToken(tokens, index)
|
||||||
|
const { elements, lastIndex } = makeArrayElements(
|
||||||
|
tokens,
|
||||||
|
firstElementToken.index
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
expression: {
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
start: openingBraceToken.start,
|
||||||
|
end: tokens[lastIndex].end,
|
||||||
|
elements,
|
||||||
|
},
|
||||||
|
lastIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface BinaryExpression extends GeneralStatement {
|
export interface BinaryExpression extends GeneralStatement {
|
||||||
type: 'BinaryExpression'
|
type: 'BinaryExpression'
|
||||||
operator: string
|
operator: string
|
||||||
@ -554,9 +625,9 @@ function makePipeBody(
|
|||||||
let value: Value
|
let value: Value
|
||||||
let lastIndex: number
|
let lastIndex: number
|
||||||
if (currentToken.type === 'operator') {
|
if (currentToken.type === 'operator') {
|
||||||
const beep = makeValue(tokens, expressionStart.index)
|
const val = makeValue(tokens, expressionStart.index)
|
||||||
value = beep.value
|
value = val.value
|
||||||
lastIndex = beep.lastIndex
|
lastIndex = val.lastIndex
|
||||||
} else if (currentToken.type === 'brace' && currentToken.value === '{') {
|
} else if (currentToken.type === 'brace' && currentToken.value === '{') {
|
||||||
const sketch = makeSketchExpression(tokens, index)
|
const sketch = makeSketchExpression(tokens, index)
|
||||||
value = sketch.expression
|
value = sketch.expression
|
||||||
|
@ -180,6 +180,16 @@ show(mySketch)
|
|||||||
// sourceRange: [77, 86],
|
// sourceRange: [77, 86],
|
||||||
// })
|
// })
|
||||||
})
|
})
|
||||||
|
it('execute array expression', () => {
|
||||||
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
|
'\n'
|
||||||
|
)
|
||||||
|
const { root } = exe(code)
|
||||||
|
expect(root).toEqual({
|
||||||
|
three: 3,
|
||||||
|
yo: [1, '2', 3, 9],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -43,6 +43,24 @@ export const executor = (
|
|||||||
declaration.init,
|
declaration.init,
|
||||||
_programMemory
|
_programMemory
|
||||||
)
|
)
|
||||||
|
} else if (declaration.init.type === 'ArrayExpression') {
|
||||||
|
_programMemory.root[variableName] = declaration.init.elements.map(
|
||||||
|
(element) => {
|
||||||
|
if (element.type === 'Literal') {
|
||||||
|
return element.value
|
||||||
|
} else if (element.type === 'BinaryExpression') {
|
||||||
|
return getBinaryExpressionResult(element, _programMemory)
|
||||||
|
} else if (element.type === 'PipeExpression') {
|
||||||
|
return getPipeExpressionResult(element, _programMemory)
|
||||||
|
} else if (element.type === 'Identifier') {
|
||||||
|
return _programMemory.root[element.name]
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected element type ${element.type} in array expression`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (declaration.init.type === 'SketchExpression') {
|
} else if (declaration.init.type === 'SketchExpression') {
|
||||||
const sketchInit = declaration.init
|
const sketchInit = declaration.init
|
||||||
const fnMemory: ProgramMemory = {
|
const fnMemory: ProgramMemory = {
|
||||||
|
@ -99,6 +99,29 @@ show(mySketch)
|
|||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code.trim())
|
expect(recasted).toBe(code.trim())
|
||||||
})
|
})
|
||||||
|
it('recast array declaration', () => {
|
||||||
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
|
'\n'
|
||||||
|
)
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
|
it('recast long array declaration', () => {
|
||||||
|
const code = [
|
||||||
|
'const three = 3',
|
||||||
|
'const yo = [',
|
||||||
|
' 1,',
|
||||||
|
" '2',",
|
||||||
|
' three,',
|
||||||
|
' 4 + 5,',
|
||||||
|
" 'hey oooooo really long long long'",
|
||||||
|
']',
|
||||||
|
].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code.trim())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
Value,
|
Value,
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
SketchExpression,
|
SketchExpression,
|
||||||
|
ArrayExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
|
|
||||||
export function recast(
|
export function recast(
|
||||||
@ -19,6 +20,8 @@ export function recast(
|
|||||||
if (statement.type === 'ExpressionStatement') {
|
if (statement.type === 'ExpressionStatement') {
|
||||||
if (statement.expression.type === 'BinaryExpression') {
|
if (statement.expression.type === 'BinaryExpression') {
|
||||||
return indentation + recastBinaryExpression(statement.expression)
|
return indentation + recastBinaryExpression(statement.expression)
|
||||||
|
} else if (statement.expression.type === 'ArrayExpression') {
|
||||||
|
return indentation + recastArrayExpression(statement.expression)
|
||||||
} else if (statement.expression.type === 'CallExpression') {
|
} else if (statement.expression.type === 'CallExpression') {
|
||||||
return indentation + recastCallExpression(statement.expression)
|
return indentation + recastCallExpression(statement.expression)
|
||||||
}
|
}
|
||||||
@ -52,6 +55,25 @@ function recastBinaryExpression(expression: BinaryExpression): string {
|
|||||||
} ${recastBinaryPart(expression.right)}`
|
} ${recastBinaryPart(expression.right)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recastArrayExpression(
|
||||||
|
expression: ArrayExpression,
|
||||||
|
indentation = ''
|
||||||
|
): string {
|
||||||
|
const flatRecast = `[${expression.elements
|
||||||
|
.map((el) => recastValue(el))
|
||||||
|
.join(', ')}]`
|
||||||
|
const maxArrayLength = 40
|
||||||
|
if (flatRecast.length > maxArrayLength) {
|
||||||
|
const _indentation = indentation + ' '
|
||||||
|
return `[
|
||||||
|
${_indentation}${expression.elements
|
||||||
|
.map((el) => recastValue(el))
|
||||||
|
.join(`,\n${_indentation}`)}
|
||||||
|
]`
|
||||||
|
}
|
||||||
|
return flatRecast
|
||||||
|
}
|
||||||
|
|
||||||
function recastBinaryPart(part: BinaryPart): string {
|
function recastBinaryPart(part: BinaryPart): string {
|
||||||
if (part.type === 'Literal') {
|
if (part.type === 'Literal') {
|
||||||
return recastLiteral(part)
|
return recastLiteral(part)
|
||||||
@ -82,6 +104,8 @@ function recastArgument(argument: Value): string {
|
|||||||
return argument.name
|
return argument.name
|
||||||
} else if (argument.type === 'BinaryExpression') {
|
} else if (argument.type === 'BinaryExpression') {
|
||||||
return recastBinaryExpression(argument)
|
return recastBinaryExpression(argument)
|
||||||
|
} else if (argument.type === 'ArrayExpression') {
|
||||||
|
return recastArrayExpression(argument)
|
||||||
} else if (argument.type === 'CallExpression') {
|
} else if (argument.type === 'CallExpression') {
|
||||||
return recastCallExpression(argument)
|
return recastCallExpression(argument)
|
||||||
} else if (argument.type === 'FunctionExpression') {
|
} else if (argument.type === 'FunctionExpression') {
|
||||||
@ -110,12 +134,16 @@ ${recast(expression.body, '', indentation + ' ')}
|
|||||||
function recastValue(node: Value, indentation = ''): string {
|
function recastValue(node: Value, indentation = ''): string {
|
||||||
if (node.type === 'BinaryExpression') {
|
if (node.type === 'BinaryExpression') {
|
||||||
return recastBinaryExpression(node)
|
return recastBinaryExpression(node)
|
||||||
|
} else if (node.type === 'ArrayExpression') {
|
||||||
|
return recastArrayExpression(node, indentation)
|
||||||
} else if (node.type === 'Literal') {
|
} else if (node.type === 'Literal') {
|
||||||
return recastLiteral(node)
|
return recastLiteral(node)
|
||||||
} else if (node.type === 'FunctionExpression') {
|
} else if (node.type === 'FunctionExpression') {
|
||||||
return recastFunction(node)
|
return recastFunction(node)
|
||||||
} else if (node.type === 'CallExpression') {
|
} else if (node.type === 'CallExpression') {
|
||||||
return recastCallExpression(node)
|
return recastCallExpression(node)
|
||||||
|
} else if (node.type === 'Identifier') {
|
||||||
|
return node.name
|
||||||
} else if (node.type === 'SketchExpression') {
|
} else if (node.type === 'SketchExpression') {
|
||||||
return recastSketchExpression(node, indentation)
|
return recastSketchExpression(node, indentation)
|
||||||
} else if (node.type === 'PipeExpression') {
|
} else if (node.type === 'PipeExpression') {
|
||||||
|
@ -332,6 +332,23 @@ describe('testing lexer', () => {
|
|||||||
"brace ')' from 54 to 55",
|
"brace ')' from 54 to 55",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
it('testing array declaration', () => {
|
||||||
|
const result = stringSummaryLexer(`const yo = [1, 2]`)
|
||||||
|
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",
|
||||||
|
"number '1' from 12 to 13",
|
||||||
|
"comma ',' from 13 to 14",
|
||||||
|
"whitespace ' ' from 14 to 15",
|
||||||
|
"number '2' from 15 to 16",
|
||||||
|
"brace ']' from 16 to 17",
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -12,6 +12,8 @@ const BLOCK_START = /^\{/
|
|||||||
const BLOCK_END = /^\}/
|
const BLOCK_END = /^\}/
|
||||||
const PARAN_START = /^\(/
|
const PARAN_START = /^\(/
|
||||||
const PARAN_END = /^\)/
|
const PARAN_END = /^\)/
|
||||||
|
const ARRAY_START = /^\[/
|
||||||
|
const ARRAY_END = /^\]/
|
||||||
const COMMA = /^,/
|
const COMMA = /^,/
|
||||||
|
|
||||||
export const isNumber = (character: string) => NUMBER.test(character)
|
export const isNumber = (character: string) => NUMBER.test(character)
|
||||||
@ -23,6 +25,8 @@ export const isBlockStart = (character: string) => BLOCK_START.test(character)
|
|||||||
export const isBlockEnd = (character: string) => BLOCK_END.test(character)
|
export const isBlockEnd = (character: string) => BLOCK_END.test(character)
|
||||||
export const isParanStart = (character: string) => PARAN_START.test(character)
|
export const isParanStart = (character: string) => PARAN_START.test(character)
|
||||||
export const isParanEnd = (character: string) => PARAN_END.test(character)
|
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 isComma = (character: string) => COMMA.test(character)
|
||||||
|
|
||||||
function matchFirst(str: string, regex: RegExp) {
|
function matchFirst(str: string, regex: RegExp) {
|
||||||
@ -75,6 +79,12 @@ const returnTokenAtIndex = (str: string, startIndex: number): Token | null => {
|
|||||||
if (isBlockEnd(strFromIndex)) {
|
if (isBlockEnd(strFromIndex)) {
|
||||||
return makeToken('brace', matchFirst(strFromIndex, BLOCK_END), startIndex)
|
return makeToken('brace', matchFirst(strFromIndex, BLOCK_END), startIndex)
|
||||||
}
|
}
|
||||||
|
if (isArrayStart(strFromIndex)) {
|
||||||
|
return makeToken('brace', matchFirst(strFromIndex, ARRAY_START), startIndex)
|
||||||
|
}
|
||||||
|
if (isArrayEnd(strFromIndex)) {
|
||||||
|
return makeToken('brace', matchFirst(strFromIndex, ARRAY_END), startIndex)
|
||||||
|
}
|
||||||
if (isComma(strFromIndex)) {
|
if (isComma(strFromIndex)) {
|
||||||
return makeToken('comma', matchFirst(strFromIndex, COMMA), startIndex)
|
return makeToken('comma', matchFirst(strFromIndex, COMMA), startIndex)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user