From 6ca843526f308b50304d14505ecaff161aaa3402 Mon Sep 17 00:00:00 2001 From: xpyctumo Date: Thu, 26 Dec 2024 13:19:16 +0000 Subject: [PATCH] test: Removed `as T` in random AST generators. Add simple tests for primitives --- src/test/prettyPrint/expressions.spec.ts | 139 +++++---------------- src/test/prettyPrint/primitives.spec.ts | 32 +++++ src/test/utils/expression/randomAst.ts | 147 +++++++++++++++++++++++ 3 files changed, 209 insertions(+), 109 deletions(-) create mode 100644 src/test/prettyPrint/primitives.spec.ts create mode 100644 src/test/utils/expression/randomAst.ts diff --git a/src/test/prettyPrint/expressions.spec.ts b/src/test/prettyPrint/expressions.spec.ts index b9256ae3a..3da01c21a 100644 --- a/src/test/prettyPrint/expressions.spec.ts +++ b/src/test/prettyPrint/expressions.spec.ts @@ -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; - - const generateAstOpUnary = (expression: fc.Arbitrary) => - fc.record({ - kind: fc.constant("op_unary"), - op: fc.constantFrom("+", "-", "!", "!!", "~"), - operand: expression, - id: fc.constant(0), - loc: fc.constant(dummySrcInfo), - }) as fc.Arbitrary; - - const generateAstOpBinary = (expression: fc.Arbitrary) => - 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; - - const generateAstConditional = (expression: fc.Arbitrary) => - fc.record({ - kind: fc.constant("conditional"), - condition: expression, - thenBranch: expression, - elseBranch: expression, - id: fc.constant(0), - loc: fc.constant(dummySrcInfo), - }) as fc.Arbitrary; + const cases: [string, fc.Arbitrary][] = [ + [ + "AstConditional", + randomAstConditional(expression(), expression(), expression()), + ], + ["AstOpBinary", randomAstOpBinary(expression(), expression())], + ["AstOpUnary", randomAstOpUnary(expression())], + ]; - const generateAstExpression: fc.Arbitrary = fc.letrec( - (tie) => ({ - AstExpression: fc.oneof( - generateAstNumber(), - tie("AstOpUnary") as fc.Arbitrary, - tie("AstOpBinary") as fc.Arbitrary, - tie("AstConditional") as fc.Arbitrary, - ), - AstOpUnary: fc.limitShrink( - generateAstOpUnary( - tie("AstExpression") as fc.Arbitrary, - ), - maxShrinks, - ), - AstOpBinary: fc.limitShrink( - generateAstOpBinary( - tie("AstExpression") as fc.Arbitrary, - ), - maxShrinks, - ), - AstConditional: fc.limitShrink( - generateAstConditional( - tie("AstExpression") as fc.Arbitrary, - ), - 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 }, + ); + }); }); }); diff --git a/src/test/prettyPrint/primitives.spec.ts b/src/test/prettyPrint/primitives.spec.ts new file mode 100644 index 000000000..52257c223 --- /dev/null +++ b/src/test/prettyPrint/primitives.spec.ts @@ -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][] = [ + ["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 }, + ); + }); + }); +}); diff --git a/src/test/utils/expression/randomAst.ts b/src/test/utils/expression/randomAst.ts new file mode 100644 index 000000000..ed97c1467 --- /dev/null +++ b/src/test/utils/expression/randomAst.ts @@ -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( + generator: fc.Arbitrary, +): fc.Arbitrary { + return generator.map((i) => ({ + ...i, + id: 0, + loc: dummySrcInfo, + })); +} + +export function randomAstBoolean(): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("boolean"), + value: fc.boolean(), + }), + ); +} + +export function randomAstString(): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("string"), + value: fc.string(), + }), + ); +} + +export function randomAstNumber(): fc.Arbitrary { + 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, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("op_unary"), + op: fc.constantFrom("+", "-", "!", "!!", "~"), + operand: operand, + }), + ); +} +export function randomAstOpBinary( + leftExpression: fc.Arbitrary, + rightExpression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("op_binary"), + op: fc.constantFrom( + "+", + "-", + "*", + "/", + "!=", + ">", + "<", + ">=", + "<=", + "==", + "&&", + "||", + "%", + "<<", + ">>", + "&", + "|", + "^", + ), + left: leftExpression, + right: rightExpression, + }), + ); +} + +export function randomAstConditional( + conditionExpression: fc.Arbitrary, + thenBranchExpression: fc.Arbitrary, + elseBranchExpression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("conditional"), + condition: conditionExpression, + thenBranch: thenBranchExpression, + elseBranch: elseBranchExpression, + }), + ); +} + +export function randomAstExpression( + maxShrinks: number, +): fc.Arbitrary { + 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; +}