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
|
||||
| PipeExpression
|
||||
| PipeSubstitution
|
||||
| ArrayExpression
|
||||
|
||||
function makeValue(
|
||||
tokens: Token[],
|
||||
@ -379,6 +380,16 @@ function makeVariableDeclarators(
|
||||
const callExInfo = makeCallExpression(tokens, contentsStartToken.index)
|
||||
init = callExInfo.expression
|
||||
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 {
|
||||
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 {
|
||||
type: 'BinaryExpression'
|
||||
operator: string
|
||||
@ -554,9 +625,9 @@ function makePipeBody(
|
||||
let value: Value
|
||||
let lastIndex: number
|
||||
if (currentToken.type === 'operator') {
|
||||
const beep = makeValue(tokens, expressionStart.index)
|
||||
value = beep.value
|
||||
lastIndex = beep.lastIndex
|
||||
const val = makeValue(tokens, expressionStart.index)
|
||||
value = val.value
|
||||
lastIndex = val.lastIndex
|
||||
} else if (currentToken.type === 'brace' && currentToken.value === '{') {
|
||||
const sketch = makeSketchExpression(tokens, index)
|
||||
value = sketch.expression
|
||||
|
@ -180,6 +180,16 @@ show(mySketch)
|
||||
// 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
|
||||
|
@ -43,6 +43,24 @@ export const executor = (
|
||||
declaration.init,
|
||||
_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') {
|
||||
const sketchInit = declaration.init
|
||||
const fnMemory: ProgramMemory = {
|
||||
|
@ -99,6 +99,29 @@ show(mySketch)
|
||||
const recasted = recast(ast)
|
||||
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
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
Value,
|
||||
FunctionExpression,
|
||||
SketchExpression,
|
||||
ArrayExpression,
|
||||
} from './abstractSyntaxTree'
|
||||
|
||||
export function recast(
|
||||
@ -19,6 +20,8 @@ export function recast(
|
||||
if (statement.type === 'ExpressionStatement') {
|
||||
if (statement.expression.type === 'BinaryExpression') {
|
||||
return indentation + recastBinaryExpression(statement.expression)
|
||||
} else if (statement.expression.type === 'ArrayExpression') {
|
||||
return indentation + recastArrayExpression(statement.expression)
|
||||
} else if (statement.expression.type === 'CallExpression') {
|
||||
return indentation + recastCallExpression(statement.expression)
|
||||
}
|
||||
@ -52,6 +55,25 @@ function recastBinaryExpression(expression: BinaryExpression): string {
|
||||
} ${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 {
|
||||
if (part.type === 'Literal') {
|
||||
return recastLiteral(part)
|
||||
@ -82,6 +104,8 @@ function recastArgument(argument: Value): string {
|
||||
return argument.name
|
||||
} else if (argument.type === 'BinaryExpression') {
|
||||
return recastBinaryExpression(argument)
|
||||
} else if (argument.type === 'ArrayExpression') {
|
||||
return recastArrayExpression(argument)
|
||||
} else if (argument.type === 'CallExpression') {
|
||||
return recastCallExpression(argument)
|
||||
} else if (argument.type === 'FunctionExpression') {
|
||||
@ -110,12 +134,16 @@ ${recast(expression.body, '', indentation + ' ')}
|
||||
function recastValue(node: Value, indentation = ''): string {
|
||||
if (node.type === 'BinaryExpression') {
|
||||
return recastBinaryExpression(node)
|
||||
} else if (node.type === 'ArrayExpression') {
|
||||
return recastArrayExpression(node, indentation)
|
||||
} else if (node.type === 'Literal') {
|
||||
return recastLiteral(node)
|
||||
} else if (node.type === 'FunctionExpression') {
|
||||
return recastFunction(node)
|
||||
} else if (node.type === 'CallExpression') {
|
||||
return recastCallExpression(node)
|
||||
} else if (node.type === 'Identifier') {
|
||||
return node.name
|
||||
} else if (node.type === 'SketchExpression') {
|
||||
return recastSketchExpression(node, indentation)
|
||||
} else if (node.type === 'PipeExpression') {
|
||||
|
@ -332,6 +332,23 @@ describe('testing lexer', () => {
|
||||
"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
|
||||
|
@ -12,6 +12,8 @@ const BLOCK_START = /^\{/
|
||||
const BLOCK_END = /^\}/
|
||||
const PARAN_START = /^\(/
|
||||
const PARAN_END = /^\)/
|
||||
const ARRAY_START = /^\[/
|
||||
const ARRAY_END = /^\]/
|
||||
const COMMA = /^,/
|
||||
|
||||
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 isParanStart = (character: string) => PARAN_START.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)
|
||||
|
||||
function matchFirst(str: string, regex: RegExp) {
|
||||
@ -75,6 +79,12 @@ const returnTokenAtIndex = (str: string, startIndex: number): Token | null => {
|
||||
if (isBlockEnd(strFromIndex)) {
|
||||
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)) {
|
||||
return makeToken('comma', matchFirst(strFromIndex, COMMA), startIndex)
|
||||
}
|
||||
|
Reference in New Issue
Block a user