From 9d957f0fe7ce7fe36e27fe8cac039e5da5c6a838 Mon Sep 17 00:00:00 2001 From: xpyctumo Date: Thu, 26 Dec 2024 13:33:05 +0000 Subject: [PATCH] test: Start validate parser with random generated AST expressions --- cspell.json | 3 +- package.json | 1 + src/test/prettyPrint/expressions.spec.ts | 119 +++++++++++++++++++++++ yarn.lock | 9 +- 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/test/prettyPrint/expressions.spec.ts diff --git a/cspell.json b/cspell.json index 87f925c8f..66d4922de 100644 --- a/cspell.json +++ b/cspell.json @@ -130,7 +130,8 @@ "Ints", "workchain", "xffff", - "привет" + "привет", + "letrec" ], "ignoreRegExpList": [ "\\b[xB]\\{[a-fA-F0-9]*_?\\}", // binary literals in Fift-asm diff --git a/package.json b/package.json index 5751fcbd3..c1458d3e2 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "cross-env": "^7.0.3", "cspell": "^8.8.3", "eslint": "^8.56.0", + "fast-check": "^3.23.2", "glob": "^8.1.0", "husky": "^9.1.5", "jest": "^29.3.1", diff --git a/src/test/prettyPrint/expressions.spec.ts b/src/test/prettyPrint/expressions.spec.ts new file mode 100644 index 000000000..b9256ae3a --- /dev/null +++ b/src/test/prettyPrint/expressions.spec.ts @@ -0,0 +1,119 @@ +import fc from "fast-check"; +import { + AstConditional, + AstExpression, + AstNumber, + AstOpBinary, + AstOpUnary, + eqExpressions, + getAstFactory, +} from "../../grammar/ast"; +import { prettyPrint } from "../../prettyPrinter"; +import { dummySrcInfo, getParser } from "../../grammar"; + +describe("Pretty Print Expressions", () => { + // Max depth of the expression tree + const maxShrinks = 15; + + 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 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 }, + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 68198260e..425cc8060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2756,6 +2756,13 @@ external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" +fast-check@^3.23.2: + version "3.23.2" + resolved "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz#0129f1eb7e4f500f58e8290edc83c670e4a574a2" + integrity sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A== + dependencies: + pure-rand "^6.1.0" + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz" @@ -4795,7 +4802,7 @@ pupa@^3.1.0: dependencies: escape-goat "^4.0.0" -pure-rand@^6.0.0: +pure-rand@^6.0.0, pure-rand@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==