add array declarations

This commit is contained in:
Kurt Hutten IrevDev
2022-12-30 21:53:50 +11:00
parent f6c4250947
commit 3d6f5982c2
8 changed files with 253 additions and 3 deletions

View File

@ -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',
},
},
],
},
},
],
},
])
})
}) })

View File

@ -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

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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') {

View File

@ -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

View File

@ -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)
} }