Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 127 additions & 89 deletions src/ast/utils/astToString.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Node } from '../types/ast';
import { NativeDeclaration } from '../../ec-evaluator'
import { Node } from '../types/ast'
import {
Assignment,
BinaryExpression,
Expand All @@ -10,122 +11,159 @@ import {
Literal,
LocalVariableDeclarationStatement,
MethodInvocation,
ReturnStatement,
} from '../types/blocks-and-statements';
ReturnStatement
} from '../types/blocks-and-statements'
import {
ConstructorDeclaration,
FieldDeclaration,
MethodDeclaration,
NormalClassDeclaration,
} from '../types/classes';
NormalClassDeclaration
} from '../types/classes'

const newline = (line: string): string => {
return `${line}\n`;
return `${line}\n`
}

const indentLine = (lvl: number, line: string): string => {
return `${' '.repeat(lvl)}${line}`;
return `${' '.repeat(lvl)}${line}`
}

const INDENT_SPACES = 2;
const INDENT_SPACES = 2

export const astToString = (node: Node, indent: number = 0): string => {
export const astToString = (node: Node | NativeDeclaration, indent: number = 0): string => {
switch (node.kind) {
case "NormalClassDeclaration":
const c = node as NormalClassDeclaration;
const cModifier = c.classModifier.length > 0 ? c.classModifier.join(" ") + " " : "";
const cSuperclass = c.sclass ? " extends " + c.sclass : "";
return newline(indentLine(indent, `${cModifier}class ${c.typeIdentifier}${cSuperclass} {`))
+ `${c.classBody.map(b => newline(astToString(b, indent + INDENT_SPACES))).join("")}`
+ `${indentLine(indent, "}")}`;

case "Block":
const block = node as Block;
return `${newline(indentLine(indent, "{"))}`
+ `${block.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join("")}`
+ `${indentLine(indent, "}")}`;

case "ConstructorDeclaration":
const con = node as ConstructorDeclaration;
const conModifier = con.constructorModifier.length > 0
? con.constructorModifier.join(" ") + " "
: "";
const conIdentifier = con.constructorDeclarator.identifier;
const conParams = con.constructorDeclarator.formalParameterList.map(p => `${p.unannType} ${p.identifier}`).join(", ");
return newline(indentLine(indent, `${conModifier}${conIdentifier}(${conParams}) {`))
+ `${con.constructorBody.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join("")}`
+ `${indentLine(indent, "}")}`;

case "MethodDeclaration":
const mtd = node as MethodDeclaration;
const mtdModifier = mtd.methodModifier.length > 0 ? mtd.methodModifier.join(" ") + " " : "";
const mtdRes = mtd.methodHeader.result;
const mtdIdentifier = mtd.methodHeader.identifier;
const mtdParams = mtd.methodHeader.formalParameterList.map(p => `${p.unannType} ${p.identifier}`).join(", ");
return newline(indentLine(indent, `${mtdModifier}${mtdRes} ${mtdIdentifier}(${mtdParams}) {`))
+ `${mtd.methodBody.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join("")}`
+ `${indentLine(indent, "}")}`;

case "FieldDeclaration":
const field = node as FieldDeclaration;
const fieldModifier = field.fieldModifier.length > 0 ? field.fieldModifier.join(" ") + " " : "";
const fieldType = field.fieldType;
const fieldIdentifier = field.variableDeclaratorList[0].variableDeclaratorId;
case 'NormalClassDeclaration':
const c = node as NormalClassDeclaration
const cModifier = c.classModifier.length > 0 ? c.classModifier.join(' ') + ' ' : ''
const cSuperclass = c.sclass ? ' extends ' + c.sclass : ''
return (
newline(indentLine(indent, `${cModifier}class ${c.typeIdentifier}${cSuperclass} {`)) +
`${c.classBody.map(b => newline(astToString(b, indent + INDENT_SPACES))).join('')}` +
`${indentLine(indent, '}')}`
)

case 'Block':
const block = node as Block
return (
`${newline(indentLine(indent, '{'))}` +
`${block.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join('')}` +
`${indentLine(indent, '}')}`
)

case 'ConstructorDeclaration':
const con = node as ConstructorDeclaration
const conModifier =
con.constructorModifier.length > 0 ? con.constructorModifier.join(' ') + ' ' : ''
const conIdentifier = con.constructorDeclarator.identifier
const conParams = con.constructorDeclarator.formalParameterList
.map(p => `${p.unannType} ${p.identifier}`)
.join(', ')
return (
newline(indentLine(indent, `${conModifier}${conIdentifier}(${conParams}) {`)) +
`${con.constructorBody.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join('')}` +
`${indentLine(indent, '}')}`
)

case 'MethodDeclaration':
const normalMtd = node as MethodDeclaration
const normalMtdModifier =
normalMtd.methodModifier.length > 0 ? normalMtd.methodModifier.join(' ') + ' ' : ''
const normalMtdRes = normalMtd.methodHeader.result
const normalMtdIdentifier = normalMtd.methodHeader.identifier
const normalMtdParams = normalMtd.methodHeader.formalParameterList
.map(p => `${p.unannType} ${p.identifier}`)
.join(', ')
return (
newline(
indentLine(
indent,
`${normalMtdModifier}${normalMtdRes} ${normalMtdIdentifier}(${normalMtdParams}) {`
)
) +
`${normalMtd.methodBody.blockStatements.map(s => newline(astToString(s, indent + INDENT_SPACES))).join('')}` +
`${indentLine(indent, '}')}`
)

case 'NativeDeclaration':
const nativeMtd = node as NativeDeclaration
const nativeMtdModifier =
nativeMtd.methodModifier.length > 0 ? nativeMtd.methodModifier.join(' ') + ' ' : ''
const nativeMtdRes = nativeMtd.methodHeader.result
const nativeMtdIdentifier = nativeMtd.methodHeader.identifier
const nativeMtdParams = nativeMtd.methodHeader.formalParameterList
.map(p => `${p.unannType} ${p.identifier}`)
.join(', ')
return newline(
indentLine(
indent,
`${nativeMtdModifier}${nativeMtdRes} ${nativeMtdIdentifier}(${nativeMtdParams});`
)
)

case 'FieldDeclaration':
const field = node as FieldDeclaration
const fieldModifier =
field.fieldModifier.length > 0 ? field.fieldModifier.join(' ') + ' ' : ''
const fieldType = field.fieldType
const fieldIdentifier = field.variableDeclaratorList[0].variableDeclaratorId
const fieldInit = field.variableDeclaratorList[0].variableInitializer
? ` = ${astToString(field.variableDeclaratorList[0].variableInitializer! as ExpressionName)}`
: "";
return indentLine(indent, `${fieldModifier}${fieldType} ${fieldIdentifier}${fieldInit};`);
: ''
return indentLine(indent, `${fieldModifier}${fieldType} ${fieldIdentifier}${fieldInit};`)

case "LocalVariableDeclarationStatement":
const localVar = node as LocalVariableDeclarationStatement;
const localVarType = localVar.localVariableType;
const localVarIdentifier = localVar.variableDeclaratorList[0].variableDeclaratorId;
case 'LocalVariableDeclarationStatement':
const localVar = node as LocalVariableDeclarationStatement
const localVarType = localVar.localVariableType
const localVarIdentifier = localVar.variableDeclaratorList[0].variableDeclaratorId
const localVarInit = localVar.variableDeclaratorList[0].variableInitializer
? ` = ${astToString(localVar.variableDeclaratorList[0].variableInitializer! as ExpressionName)}`
: "";
return indentLine(indent, `${localVarType} ${localVarIdentifier}${localVarInit};`);
: ''
return indentLine(indent, `${localVarType} ${localVarIdentifier}${localVarInit};`)

case "ReturnStatement":
const returnStmt = node as ReturnStatement;
const returnExp = returnStmt.exp.kind === "Void" ? "" : ` ${astToString(returnStmt.exp)}`;
return indentLine(indent, `return${returnExp};`);
case 'ReturnStatement':
const returnStmt = node as ReturnStatement
const returnExp = returnStmt.exp.kind === 'Void' ? '' : ` ${astToString(returnStmt.exp)}`
return indentLine(indent, `return${returnExp};`)

case "ExpressionStatement":
const expStmt = node as ExpressionStatement;
return indentLine(indent, `${astToString(expStmt.stmtExp)};`);
case 'ExpressionStatement':
const expStmt = node as ExpressionStatement
return indentLine(indent, `${astToString(expStmt.stmtExp)};`)

case "Assignment":
const assmt = node as Assignment;
return `${(assmt.left as ExpressionName).name} = ${astToString(assmt.right)}`;
case 'Assignment':
const assmt = node as Assignment
return `${(assmt.left as ExpressionName).name} = ${astToString(assmt.right)}`

case "MethodInvocation":
const mtdInv = node as MethodInvocation;
return `${mtdInv.identifier}(${mtdInv.argumentList.map(a => astToString(a)).join(", ")})`
case 'MethodInvocation':
const mtdInv = node as MethodInvocation
return `${mtdInv.identifier}(${mtdInv.argumentList.map(a => astToString(a)).join(', ')})`

case "ClassInstanceCreationExpression":
const classInstanceCreationExp = node as ClassInstanceCreationExpression;
return `new ${classInstanceCreationExp.identifier}(${classInstanceCreationExp.argumentList.map(a => astToString(a))})`;
case 'ClassInstanceCreationExpression':
const classInstanceCreationExp = node as ClassInstanceCreationExpression
return `new ${classInstanceCreationExp.identifier}(${classInstanceCreationExp.argumentList.map(a => astToString(a))})`

case "ExplicitConstructorInvocation":
const expConInv = node as ExplicitConstructorInvocation;
return indentLine(indent, `${expConInv.thisOrSuper}(${expConInv.argumentList.map(a => astToString(a))});`);
case 'ExplicitConstructorInvocation':
const expConInv = node as ExplicitConstructorInvocation
return indentLine(
indent,
`${expConInv.thisOrSuper}(${expConInv.argumentList.map(a => astToString(a))});`
)

case "BinaryExpression":
const bin = node as BinaryExpression;
return `${astToString(bin.left)} ${bin.operator} ${astToString(bin.right)}`;
case 'BinaryExpression':
const bin = node as BinaryExpression
return `${astToString(bin.left)} ${bin.operator} ${astToString(bin.right)}`

case "ExpressionName":
const exp = node as ExpressionName;
return exp.name;
case 'ExpressionName':
const exp = node as ExpressionName
return exp.name

case "Literal":
const literal = node as Literal;
return `${literal.literalType.value}`;
case 'Literal':
const literal = node as Literal
return `${literal.literalType.value}`

case "Void":
return "void";
case 'Void':
return 'void'

default:
return node.kind;
return node.kind
}
}
}
35 changes: 35 additions & 0 deletions src/ec-evaluator/__tests__/__utils__/library.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { runECEvaluator } from '../..'
import { Result } from '../../types'

export type LibraryRun = { result: Result; out: string[]; err: string[] }

/** Runs a program through the public entry point, capturing stdout and stderr. */
export const run = async (code: string, targetStep?: number): Promise<LibraryRun> => {
const out: string[] = []
const err: string[] = []
const result = await runECEvaluator(code, targetStep, {
stdout: s => out.push(s),
stderr: (type, msg) => err.push(`${type}: ${msg}`)
})
return { result, out, err }
}

/** Wraps statements in a `Main.main`, with optional extra top-level classes. */
export const program = (body: string, classes: string = '') => `
${classes}
public class Main {
public static void main(String[] args) {
${body}
}
}`

/** Every prefix run must print a prefix of the full run's output: the re-run stepping model. */
export const expectStableAcrossSteps = async (code: string) => {
const full = await run(code)
expect(full.result.status).toBe('finished')
for (let step = 0; step <= full.result.context.totalSteps; step++) {
const partial = await run(code, step)
expect(partial.out).toEqual(full.out.slice(0, partial.out.length))
}
return full
}
75 changes: 45 additions & 30 deletions src/ec-evaluator/__tests__/__utils__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,66 @@
import { Environment } from "../../components";
import { STEP_LIMIT } from "../../constants";
import { ControlItem, Context, StashItem, StructType } from "../../types";
import { Stack, isNode } from "../../utils";
import { Environment } from '../../components'
import { STEP_LIMIT } from '../../constants'
import { LFSR } from '../../lib'
import { ControlItem, Context, StashItem, StructType } from '../../types'
import { Stack, isNode } from '../../utils'
import { makeObjectClass } from '../../index'

export class StackStub<T> extends Stack<T> {
private trace: T[] = [];
private trace: T[] = []

public push(...items: T[]): void {
for (const item of items) {
super.push(item);
this.trace.push(item);
super.push(item)
this.trace.push(item)
}
};
}

public getTrace(): T[] {
return this.trace;
};
};
export class ControlStub extends StackStub<ControlItem> {};
export class StashStub extends StackStub<StashItem> {};
return this.trace
}
}

export class ControlStub extends StackStub<ControlItem> {}
export class StashStub extends StackStub<StashItem> {}
// TODO make env traceable
export class EnvironmentStub extends Environment {};
export class EnvironmentStub extends Environment {
constructor() {
super()
this.defineClass('Object', makeObjectClass())
}
}

export const createContextStub = (): Context => ({
errors: [],
control: new ControlStub(),
stash: new StashStub(),
environment: new EnvironmentStub(),
totalSteps: STEP_LIMIT,
});
interfaces: {
stdout: console.log,
stderr: console.error,
statics: {
lfsr: new LFSR('')
}
}
})

export const getControlItemStr = (i: ControlItem): string => {
return isNode(i) ? i.kind : i.instrType;
};
return isNode(i) ? i.kind : i.instrType
}

export const getStashItemStr = (i: StashItem): string => {
return i.kind === "Literal"
? i.literalType.value
return i.kind === 'Literal'
? i.literalType.value
: i.kind === StructType.CLOSURE
? i.mtdOrCon.kind === "MethodDeclaration"
? i.mtdOrCon.methodHeader.identifier
: i.mtdOrCon.constructorDeclarator.identifier
: i.kind === StructType.VARIABLE
? i.name
: i.kind === StructType.CLASS
? i.frame.name
: i.kind === StructType.TYPE
? i.type
: i.kind;
};
? i.mtdOrCon.kind === 'MethodDeclaration' || i.mtdOrCon.kind === 'NativeDeclaration'
? i.mtdOrCon.methodHeader.identifier
: i.mtdOrCon.constructorDeclarator.identifier
: i.kind === StructType.VARIABLE
? i.name
: i.kind === StructType.CLASS
? i.frame.name
: i.kind === StructType.TYPE
? i.type
: i.kind
}
Loading
Loading