inital vesion of recast working

This commit is contained in:
Kurt Hutten IrevDev
2022-11-26 19:03:09 +11:00
parent 48e59ac710
commit 149633a5cb
3 changed files with 140 additions and 19 deletions

View File

@ -123,7 +123,7 @@ function makeExpressionStatement(
} }
} }
interface CallExpression extends GeneralStatement { export interface CallExpression extends GeneralStatement {
type: 'CallExpression' type: 'CallExpression'
callee: Identifier callee: Identifier
arguments: Value[] arguments: Value[]
@ -244,7 +244,7 @@ function makeVariableDeclaration(
} }
} }
type Value = export type Value =
| Literal | Literal
| Identifier | Identifier
| BinaryExpression | BinaryExpression
@ -371,7 +371,7 @@ export type BinaryPart = Literal | Identifier
// | LogicalExpression // | LogicalExpression
// | ConditionalExpression // | ConditionalExpression
interface Literal extends GeneralStatement { export interface Literal extends GeneralStatement {
type: 'Literal' type: 'Literal'
value: string | number | boolean | null value: string | number | boolean | null
raw: string raw: string
@ -459,7 +459,7 @@ function makeBinaryExpression(
} }
} }
interface SketchExpression extends GeneralStatement { export interface SketchExpression extends GeneralStatement {
type: 'SketchExpression' type: 'SketchExpression'
body: BlockStatement body: BlockStatement
} }
@ -483,7 +483,7 @@ function makeSketchExpression(
} }
} }
interface FunctionExpression extends GeneralStatement { export interface FunctionExpression extends GeneralStatement {
type: 'FunctionExpression' type: 'FunctionExpression'
id: Identifier | null id: Identifier | null
params: Identifier[] params: Identifier[]

View File

@ -1,8 +1,7 @@
import { recast } from './recast' import { recast } from './recast'
import { Program } from './abstractSyntaxTree' import { Program, abstractSyntaxTree } from './abstractSyntaxTree'
import { abstractSyntaxTree } from './abstractSyntaxTree' import { lexer, Token } from './tokeniser'
import { lexer } from './tokeniser' import fs from 'node:fs'
import { Token } from './tokeniser'
describe('recast', () => { describe('recast', () => {
it('recasts a simple program', () => { it('recasts a simple program', () => {
@ -26,6 +25,56 @@ describe('recast', () => {
const { ast: ast2 } = code2ast(codeWithOtherQuotes) const { ast: ast2 } = code2ast(codeWithOtherQuotes)
expect(recast(ast2)).toBe(codeWithOtherQuotes) expect(recast(ast2)).toBe(codeWithOtherQuotes)
}) })
it('test assigning two variables, the second summing with the first', () => {
const code = `const myVar = 5
const newVar = myVar + 1`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('test assigning a var by cont concatenating two strings string', () => {
const code = fs.readFileSync(
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
it('test with function call', () => {
const code = `
const myVar = "hello"
log(5, myVar)`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
it('function declaration with call', () => {
const code =
[
'fn funcN = (a, b) => {',
' return a + b',
'}',
'const theVar = 60',
'const magicNum = funcN(9, theVar)',
].join('\n')
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
it('sketch declaration', () => {
let code = `sketch mySketch {
path myPath = lineTo(0, 1)
lineTo(1, 1)
path rightPath = lineTo(1, 0)
close()
}
show(mySketch)
`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code.trim())
})
}) })
// helpers // helpers

View File

@ -1,26 +1,58 @@
import { Program, BinaryExpression, BinaryPart } from './abstractSyntaxTree' import {
Program,
BinaryExpression,
BinaryPart,
Literal,
CallExpression,
Value,
FunctionExpression,
SketchExpression,
} from './abstractSyntaxTree'
export function recast(ast: Program, previousWrittenCode = ''): string { export function recast(
ast: Program,
previousWrittenCode = '',
indentation = ''
): string {
return ast.body return ast.body
.map((statement) => { .map((statement) => {
if (statement.type === 'ExpressionStatement') { if (statement.type === 'ExpressionStatement') {
if (statement.expression.type === 'BinaryExpression') { if (statement.expression.type === 'BinaryExpression') {
return recastBinaryExpression(statement.expression) return indentation + recastBinaryExpression(statement.expression)
} else if (statement.expression.type === 'CallExpression') {
return indentation + recastCallExpression(statement.expression)
} }
} else if (statement.type === 'VariableDeclaration') { } else if (statement.type === 'VariableDeclaration') {
return statement.declarations return statement.declarations
.map((declaration) => { .map((declaration) => {
if (declaration.init.type === 'BinaryExpression') { if (declaration.init.type === 'BinaryExpression') {
return `${statement.kind} ${ return `${indentation}${statement.kind} ${
declaration.id.name declaration.id.name
} = ${recastBinaryExpression(declaration.init)}` } = ${recastBinaryExpression(declaration.init)}`
} else if (declaration.init.type === 'Literal') { } else if (declaration.init.type === 'Literal') {
return `${statement.kind} ${declaration.id.name} = ${declaration.init.value}` return `${indentation}${statement.kind} ${
declaration.id.name
} = ${recastLiteral(declaration.init)}`
} else if (declaration.init.type === 'FunctionExpression') {
return `${indentation}${statement.kind} ${
declaration.id.name
} = ${recastFunction(declaration.init)}`
} else if (declaration.init.type === 'CallExpression') {
return `${indentation}${statement.kind} ${
declaration.id.name
} = ${recastCallExpression(declaration.init)}`
} else if (declaration.init.type === 'SketchExpression') {
return `${indentation}${statement.kind} ${
declaration.id.name
} ${recastSketchExpression(declaration.init, indentation)}`
} }
return '' return ''
}) })
.join('') .join('')
} else if (statement.type === 'ReturnStatement') {
return `${indentation}return ${recastArgument(statement.argument)}`
} }
return statement.type return statement.type
}) })
.join('\n') .join('\n')
@ -34,13 +66,53 @@ function recastBinaryExpression(expression: BinaryExpression): string {
function recastBinaryPart(part: BinaryPart): string { function recastBinaryPart(part: BinaryPart): string {
if (part.type === 'Literal') { if (part.type === 'Literal') {
if (typeof part.value === 'string') { return recastLiteral(part)
const quote = part.raw.includes('"') ? '"' : "'"
return `${quote}${part.value}${quote}`
}
return String(part?.value)
} else if (part.type === 'Identifier') { } else if (part.type === 'Identifier') {
return part.name return part.name
} }
throw new Error(`Cannot recast ${part}`) throw new Error(`Cannot recast ${part}`)
} }
function recastLiteral(literal: Literal): string {
if (typeof literal.value === 'string') {
const quote = literal.raw.trim().startsWith('"') ? '"' : "'"
return `${quote}${literal.value}${quote}`
}
return String(literal?.value)
}
function recastCallExpression(expression: CallExpression): string {
return `${expression.callee.name}(${expression.arguments
.map(recastArgument)
.join(', ')})`
}
function recastArgument(argument: Value): string {
if (argument.type === 'Literal') {
return recastLiteral(argument)
} else if (argument.type === 'Identifier') {
return argument.name
} else if (argument.type === 'BinaryExpression') {
return recastBinaryExpression(argument)
} else if (argument.type === 'CallExpression') {
return recastCallExpression(argument)
} else if (argument.type === 'FunctionExpression') {
return recastFunction(argument)
}
throw new Error(`Cannot recast ${argument}`)
}
function recastFunction(expression: FunctionExpression): string {
return `(${expression.params.map((param) => param.name).join(', ')}) => {
${recast(expression.body)}
}`
}
function recastSketchExpression(
expression: SketchExpression,
indentation: string
): string {
return `{
${recast(expression.body, '', indentation + ' ')}
}`
}