Skip to content

Commit

Permalink
test: Removed as T in random AST generators. Add simple tests for p…
Browse files Browse the repository at this point in the history
…rimitives
  • Loading branch information
xpyctumo committed Dec 26, 2024
1 parent 9d957f0 commit fb01585
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 109 deletions.
139 changes: 30 additions & 109 deletions src/test/prettyPrint/expressions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,119 +1,40 @@
import fc from "fast-check";
import {
AstConditional,
AstExpression,
AstNumber,
AstOpBinary,
AstOpUnary,
eqExpressions,
getAstFactory,
} from "../../grammar/ast";
import { AstExpression, eqExpressions, getAstFactory } from "../../grammar/ast";
import { prettyPrint } from "../../prettyPrinter";
import { dummySrcInfo, getParser } from "../../grammar";
import { getParser } from "../../grammar";
import {
randomAstConditional,
randomAstOpBinary,
randomAstOpUnary,
randomAstExpression,
} from "../utils/expression/randomAst";

describe("Pretty Print Expressions", () => {
// Max depth of the expression tree
const maxShrinks = 15;
const expression = () => randomAstExpression(maxShrinks);

const generateAstNumber = () =>
fc.record({
kind: fc.constant("number"),
base: fc.constantFrom(2, 8, 10, 16),
value: fc.bigInt().filter((n) => n > 0),
id: fc.constant(0),
loc: fc.constant(dummySrcInfo),
}) as fc.Arbitrary<AstNumber>;

const generateAstOpUnary = (expression: fc.Arbitrary<AstExpression>) =>
fc.record({
kind: fc.constant("op_unary"),
op: fc.constantFrom("+", "-", "!", "!!", "~"),
operand: expression,
id: fc.constant(0),
loc: fc.constant(dummySrcInfo),
}) as fc.Arbitrary<AstOpUnary>;

const generateAstOpBinary = (expression: fc.Arbitrary<AstExpression>) =>
fc.record({
kind: fc.constant("op_binary"),
op: fc.constantFrom(
"+",
"-",
"*",
"/",
"!=",
">",
"<",
">=",
"<=",
"==",
"&&",
"||",
"%",
"<<",
">>",
"&",
"|",
"^",
),
left: expression,
right: expression,
id: fc.constant(0),
loc: fc.constant(dummySrcInfo),
}) as fc.Arbitrary<AstOpBinary>;

const generateAstConditional = (expression: fc.Arbitrary<AstExpression>) =>
fc.record({
kind: fc.constant("conditional"),
condition: expression,
thenBranch: expression,
elseBranch: expression,
id: fc.constant(0),
loc: fc.constant(dummySrcInfo),
}) as fc.Arbitrary<AstConditional>;
const cases: [string, fc.Arbitrary<AstExpression>][] = [
[
"AstConditional",
randomAstConditional(expression(), expression(), expression()),
],
["AstOpBinary", randomAstOpBinary(expression(), expression())],
["AstOpUnary", randomAstOpUnary(expression())],
];

const generateAstExpression: fc.Arbitrary<AstExpression> = fc.letrec(
(tie) => ({
AstExpression: fc.oneof(
generateAstNumber(),
tie("AstOpUnary") as fc.Arbitrary<AstOpUnary>,
tie("AstOpBinary") as fc.Arbitrary<AstOpBinary>,
tie("AstConditional") as fc.Arbitrary<AstConditional>,
),
AstOpUnary: fc.limitShrink(
generateAstOpUnary(
tie("AstExpression") as fc.Arbitrary<AstExpression>,
),
maxShrinks,
),
AstOpBinary: fc.limitShrink(
generateAstOpBinary(
tie("AstExpression") as fc.Arbitrary<AstExpression>,
),
maxShrinks,
),
AstConditional: fc.limitShrink(
generateAstConditional(
tie("AstExpression") as fc.Arbitrary<AstExpression>,
),
maxShrinks,
),
}),
).AstExpression;
it.each([
["AstConditional", generateAstConditional(generateAstExpression)],
["AstOpBinary", generateAstOpBinary(generateAstExpression)],
["AstOpUnary", generateAstOpUnary(generateAstExpression)],
])("should parse random %s expression", (_, astGenerator) => {
fc.assert(
fc.property(astGenerator, (astBefore) => {
const prettyBefore = prettyPrint(astBefore);
const astFactory = getAstFactory();
const parser = getParser(astFactory);
const astAfter = parser.parseExpression(prettyBefore);
expect(eqExpressions(astBefore, astAfter)).toBe(true);
}),
{ seed: 1 },
);
cases.forEach(([caseName, astGenerator]) => {
it(`should parse ${caseName}`, () => {
fc.assert(
fc.property(astGenerator, (generatedAst) => {
const prettyBefore = prettyPrint(generatedAst);
const astFactory = getAstFactory();
const parser = getParser(astFactory);
const parsedAst = parser.parseExpression(prettyBefore);
expect(eqExpressions(generatedAst, parsedAst)).toBe(true);
}),
{ seed: 1 },
);
});
});
});
32 changes: 32 additions & 0 deletions src/test/prettyPrint/primitives.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fc from "fast-check";
import {
randomAstBoolean,
randomAstNumber,
randomAstString,
} from "../utils/expression/randomAst";
import { getParser } from "../../grammar";
import { getAstFactory, eqExpressions, AstExpression } from "../../grammar/ast";
import { prettyPrint } from "../../prettyPrinter";

describe("Pretty Print Primitives", () => {
const cases: [string, fc.Arbitrary<AstExpression>][] = [
["AstBoolean", randomAstBoolean()],
["AstNumber", randomAstNumber()],
["AstString", randomAstString()],
];

cases.forEach(([caseName, astGenerator]) => {
it(`should parse ${caseName}`, () => {
fc.assert(
fc.property(astGenerator, (generatedAst) => {
const prettyBefore = prettyPrint(generatedAst);
const astFactory = getAstFactory();
const parser = getParser(astFactory);
const parsedAst = parser.parseExpression(prettyBefore);
expect(eqExpressions(generatedAst, parsedAst)).toBe(true);
}),
{ seed: 1 },
);
});
});
});
147 changes: 147 additions & 0 deletions src/test/utils/expression/randomAst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import fc from "fast-check";
import {
AstBoolean,
AstConditional,
AstExpression,
AstNumber,
AstOpBinary,
AstOpUnary,
AstString,
} from "../../../grammar/ast";
import { dummySrcInfo } from "../../../grammar/src-info";

function dummyAstNode<T>(
generator: fc.Arbitrary<T>,
): fc.Arbitrary<T & { id: number; loc: typeof dummySrcInfo }> {
return generator.map((i) => ({
...i,
id: 0,
loc: dummySrcInfo,
}));
}

export function randomAstBoolean(): fc.Arbitrary<AstBoolean> {
return dummyAstNode(
fc.record({
kind: fc.constant("boolean"),
value: fc.boolean(),
}),
);
}

export function randomAstString(): fc.Arbitrary<AstString> {
return dummyAstNode(
fc.record({
kind: fc.constant("string"),
value: fc.string(),
}),
);
}

export function randomAstNumber(): fc.Arbitrary<AstNumber> {
const values = [
...Array.from({ length: 10 }, (_, i) => [BigInt(i), BigInt(-i)]).flat(),
...Array.from({ length: 256 }, (_, i) => 1n ** BigInt(i)),
];

return dummyAstNode(
fc.record({
kind: fc.constant("number"),
base: fc.constantFrom(2, 8, 10, 16),
value: fc.oneof(...values.map((value) => fc.constant(value))),
}),
);
}

export function randomAstOpUnary(
operand: fc.Arbitrary<AstExpression>,
): fc.Arbitrary<AstOpUnary> {
return dummyAstNode(
fc.record({
kind: fc.constant("op_unary"),
op: fc.constantFrom("+", "-", "!", "!!", "~"),
operand: operand,
}),
);
}
export function randomAstOpBinary(
leftExpression: fc.Arbitrary<AstExpression>,
rightExpression: fc.Arbitrary<AstExpression>,
): fc.Arbitrary<AstOpBinary> {
return dummyAstNode(
fc.record({
kind: fc.constant("op_binary"),
op: fc.constantFrom(
"+",
"-",
"*",
"/",
"!=",
">",
"<",
">=",
"<=",
"==",
"&&",
"||",
"%",
"<<",
">>",
"&",
"|",
"^",
),
left: leftExpression,
right: rightExpression,
}),
);
}

export function randomAstConditional(
conditionExpression: fc.Arbitrary<AstExpression>,
thenBranchExpression: fc.Arbitrary<AstExpression>,
elseBranchExpression: fc.Arbitrary<AstExpression>,
): fc.Arbitrary<AstConditional> {
return dummyAstNode(
fc.record({
kind: fc.constant("conditional"),
condition: conditionExpression,
thenBranch: thenBranchExpression,
elseBranch: elseBranchExpression,
}),
);
}

export function randomAstExpression(
maxShrinks: number,
): fc.Arbitrary<AstExpression> {
return fc.letrec<{
AstExpression: AstExpression;
AstOpUnary: AstOpUnary;
AstOpBinary: AstOpBinary;
AstConditional: AstConditional;
}>((tie) => ({
AstExpression: fc.oneof(
randomAstNumber(), // TODO: Expand this to include more expressions, look into AstExpressionPrimary
tie("AstOpUnary"),
tie("AstOpBinary"),
tie("AstConditional"),
),
AstOpUnary: fc.limitShrink(
randomAstOpUnary(tie("AstExpression")),
maxShrinks,
),
AstOpBinary: fc.limitShrink(
randomAstOpBinary(tie("AstExpression"), tie("AstExpression")),
maxShrinks,
),
AstConditional: fc.limitShrink(
randomAstConditional(
tie("AstExpression"),
tie("AstExpression"),
tie("AstExpression"),
),
maxShrinks,
),
})).AstExpression;
}

0 comments on commit fb01585

Please sign in to comment.