├── docs ├── static │ ├── .nojekyll │ └── img │ │ └── favicon.svg ├── docs │ ├── fa │ │ ├── _category_.yml │ │ └── index.md │ ├── cfg │ │ ├── _category_.yml │ │ └── index.md │ └── testing │ │ ├── _category_.yml │ │ └── index.md ├── babel.config.js ├── scripts │ ├── tsconfig.json │ ├── package.json │ ├── package-lock.json │ └── index.ts ├── tsconfig.json ├── src │ ├── components │ │ ├── HomepageFeatures.module.css │ │ └── HomepageFeatures.tsx │ ├── pages │ │ ├── index.module.css │ │ └── index.tsx │ └── theme │ │ ├── IconDarkMode │ │ └── index.js │ │ ├── IconLightMode │ │ └── index.js │ │ └── Toggle │ │ └── styles.module.css ├── .gitignore ├── sidebars.js ├── README.md └── package.json ├── apps ├── landing │ ├── .eslintignore │ ├── public │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── apple-touch-icon.png │ ├── src │ │ └── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ └── index.tsx │ ├── postcss.config.js │ ├── styles │ │ └── main.css │ ├── next-env.d.ts │ ├── .gitignore │ ├── next.config.js │ ├── LICENSE │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ └── package.json └── playground │ ├── .eslintignore │ ├── src │ ├── utils │ │ └── index.ts │ ├── hooks │ │ └── index.ts │ ├── contexts │ │ ├── index.ts │ │ ├── DrawerContext.ts │ │ └── CfgContext.ts │ ├── pages │ │ ├── cfg │ │ │ └── generateCfgLanguage.tsx │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── index.tsx │ ├── components │ │ ├── index.ts │ │ ├── Flex.tsx │ │ ├── cfg │ │ │ └── GrammarString.tsx │ │ ├── Icons.tsx │ │ ├── Button.tsx │ │ ├── Drawer.tsx │ │ └── Select.tsx │ └── types.ts │ ├── public │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── apple-touch-icon.png │ ├── postcss.config.js │ ├── styles │ └── main.css │ ├── next-env.d.ts │ ├── .gitignore │ ├── next.config.js │ ├── tailwind.config.js │ ├── LICENSE │ ├── tsconfig.json │ ├── .eslintrc.js │ └── package.json ├── utils ├── index.ts └── equivalency.ts ├── packages ├── fa │ ├── libs │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── expandCharacterRanges.ts │ │ ├── index.ts │ │ ├── FiniteAutomaton │ │ │ ├── validate.ts │ │ │ ├── index.ts │ │ │ └── generateParseTreeForString.ts │ │ ├── DeterministicFiniteAutomaton │ │ │ ├── checkEquivalenceBetweenStatesGroups.ts │ │ │ ├── generateStateGroupsRecord.ts │ │ │ └── generateEquivalenceStates.ts │ │ ├── NonDeterministicFiniteAutomaton │ │ │ ├── epsilonClosureOfState.ts │ │ │ ├── moveAndEpsilonClosureStateSet.ts │ │ │ ├── convertToRegularNfa.ts │ │ │ └── index.ts │ │ └── types.ts │ ├── tsconfig.json │ ├── tests │ │ ├── FiniteAutomaton │ │ │ ├── validate.test.ts │ │ │ ├── generatePostNormalizationErrors.test.ts │ │ │ └── generatePreNormalizationErrors.test.ts │ │ ├── DeterministicFiniteAutomaton │ │ │ ├── generateStateGroupsRecord.test.ts │ │ │ ├── checkEquivalenceBetweenStatesGroups.test.ts │ │ │ └── generateEquivalenceStates.test.ts │ │ ├── NonDeterministicFiniteAutomaton │ │ │ ├── epsilonClosureOfState.test.ts │ │ │ ├── convertToRegularNfa.test.ts │ │ │ ├── moveAndEpsilonClosureStateSet.test.ts │ │ │ └── convertToDeterministicFiniteAutomaton.test.ts │ │ └── utils │ │ │ └── expandCharacterRanges.test.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── readme.md │ ├── .eslintrc.js │ └── package.json ├── regex │ ├── readme.md │ ├── tsconfig.json │ ├── libs │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── addConcatOperatorToRegex.ts │ │ │ └── generateRegexTree.ts │ │ ├── index.ts │ │ └── types.ts │ ├── jest.config.js │ ├── tests │ │ ├── validateRegex.test.ts │ │ ├── generateRegexTree.test.ts │ │ ├── convertInfixRegexToPostfix.test.ts │ │ └── addConcatOperatorToRegex.test.ts │ ├── .eslintrc.js │ └── package.json ├── cfg │ ├── tsconfig.json │ ├── libs │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── isAllTerminal.ts │ │ │ ├── setOperations.ts │ │ │ ├── populateCfg.ts │ │ │ ├── generateNewVariable.ts │ │ │ └── removeProductionRules.ts │ │ ├── convertGrammarToString.ts │ │ ├── removeUselessProduction.ts │ │ ├── simplifyCfg.ts │ │ ├── removeEmptyProduction.ts │ │ ├── types.ts │ │ ├── extractTerminalsFromCfg.ts │ │ ├── convertStringToGrammar.ts │ │ ├── parseWithLL1Table.ts │ │ ├── generateParseTreeFromDerivations.ts │ │ ├── generateVariableReferenceRecord.ts │ │ ├── validateCfg.ts │ │ ├── index.ts │ │ ├── generateLR0ParsingTable.ts │ │ ├── removeUnreachableProduction.ts │ │ └── generateLL1ParsingTable.ts │ ├── tests │ │ ├── utils │ │ │ ├── setOperations.test.ts │ │ │ ├── isAllTerminal.test.ts │ │ │ ├── generateNewVariable.test.ts │ │ │ └── populateCfg.test.ts │ │ ├── setEquivalency.ts │ │ ├── convertGrammarToString.test.ts │ │ ├── removeEmptyProduction.test.ts │ │ ├── removeUnreachableProduction.test.ts │ │ ├── generateParseTreeFromDerivations.test.ts │ │ ├── parseWithLL1Table.test.ts │ │ ├── convertStringToGrammar.test.ts │ │ ├── extractTerminalsFromCfg.test.ts │ │ ├── removeNonDeterminism.test.ts │ │ ├── generateLL1ParsingTable.test.ts │ │ ├── removeUnitProduction.test.ts │ │ ├── removeNonTerminableProduction.test.ts │ │ ├── removeLeftRecursion.test.ts │ │ ├── generateLR0ParsingTable.test.ts │ │ ├── generateVariableReferenceRecord.test.ts │ │ ├── validateCfg.test.ts │ │ ├── cykParse.test.ts │ │ └── removeNullProduction.test.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── readme.md │ ├── .eslintrc.js │ └── package.json └── testing │ ├── tsconfig.json │ ├── libs │ ├── utils │ │ ├── generateRandomNumber.ts │ │ ├── index.ts │ │ ├── countFileLines.ts │ │ ├── generateCaseMessage.ts │ │ ├── generateUniversalLanguage.ts │ │ ├── generateRandomLanguage.ts │ │ └── createFileWriteStreams.ts │ └── types.ts │ ├── tests │ ├── generateRandomLanguage.test.ts │ ├── generateAggregateMessage.test.ts │ └── generateUniversalLanguage.test.ts │ ├── jest.config.js │ ├── .eslintrc.js │ ├── readme.md │ ├── package.json │ └── package-lock.json ├── lerna.json ├── public ├── pre_dfa_test.png ├── epsilon_to_nfa.png ├── generated_graph.png ├── post_dfa_test.png ├── nfa_starts_with_ab.png ├── starts_with_bc_dfa.png ├── post_dfa_test_terminal.png ├── sample_case_artifact_file.png ├── sample_accepted_artifact_file.png ├── sample_aggregate_artifact_file.png ├── sample_correct_artifact_file.png └── divisible_by_3_or_2_but_not_both.jpg ├── .prettierrc.js ├── .npmignore ├── scripts ├── vercel.sh ├── uploadCoverage.sh └── build.sh ├── tsconfig.json ├── .github └── workflows │ ├── build.yaml │ └── deploy.yaml ├── LICENSE ├── package.json └── .gitignore /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/landing/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | -------------------------------------------------------------------------------- /utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './equivalency'; 2 | -------------------------------------------------------------------------------- /apps/playground/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | -------------------------------------------------------------------------------- /docs/docs/fa/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "@fauton/fa" 2 | position: 0 3 | -------------------------------------------------------------------------------- /apps/playground/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generateTheme'; 2 | -------------------------------------------------------------------------------- /docs/docs/cfg/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "@fauton/cfg" 2 | position: 0 3 | -------------------------------------------------------------------------------- /apps/playground/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useGrammarInput'; 2 | -------------------------------------------------------------------------------- /docs/docs/testing/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "@fauton/testing" 2 | position: 0 3 | -------------------------------------------------------------------------------- /packages/fa/libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './expandCharacterRanges'; 2 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /public/pre_dfa_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/pre_dfa_test.png -------------------------------------------------------------------------------- /public/epsilon_to_nfa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/epsilon_to_nfa.png -------------------------------------------------------------------------------- /public/generated_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/generated_graph.png -------------------------------------------------------------------------------- /public/post_dfa_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/post_dfa_test.png -------------------------------------------------------------------------------- /apps/landing/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/landing/public/favicon.ico -------------------------------------------------------------------------------- /packages/regex/readme.md: -------------------------------------------------------------------------------- 1 | # `@fauton/regex` 2 | 3 | A package to work with regex validation and parsing 4 | -------------------------------------------------------------------------------- /public/nfa_starts_with_ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/nfa_starts_with_ab.png -------------------------------------------------------------------------------- /public/starts_with_bc_dfa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/starts_with_bc_dfa.png -------------------------------------------------------------------------------- /apps/playground/src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CfgContext'; 2 | export * from './DrawerContext'; 3 | 4 | -------------------------------------------------------------------------------- /public/post_dfa_test_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/post_dfa_test_terminal.png -------------------------------------------------------------------------------- /apps/playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/playground/public/favicon.ico -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /public/sample_case_artifact_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/sample_case_artifact_file.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/landing/public/favicon-16x16.png -------------------------------------------------------------------------------- /apps/landing/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/landing/public/favicon-32x32.png -------------------------------------------------------------------------------- /apps/landing/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/landing/public/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/playground/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/playground/public/favicon-16x16.png -------------------------------------------------------------------------------- /apps/playground/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/playground/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/sample_accepted_artifact_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/sample_accepted_artifact_file.png -------------------------------------------------------------------------------- /public/sample_aggregate_artifact_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/sample_aggregate_artifact_file.png -------------------------------------------------------------------------------- /public/sample_correct_artifact_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/sample_correct_artifact_file.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | useTabs: true, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/playground/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/apps/playground/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/divisible_by_3_or_2_but_not_both.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devorein/fauton/HEAD/public/divisible_by_3_or_2_but_not_both.jpg -------------------------------------------------------------------------------- /apps/playground/src/pages/cfg/generateCfgLanguage.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default function GenerateCfgLanguage() { 3 | return
4 | 5 |
6 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node files 2 | package-lock.json 3 | **/*.tgz 4 | 5 | # source files 6 | **/*.ts 7 | !**/dist 8 | !**/*.d.ts 9 | !**/*.map.js 10 | **/src 11 | /workflows 12 | /.github -------------------------------------------------------------------------------- /packages/fa/libs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DeterministicFiniteAutomaton'; 2 | export * from './FiniteAutomaton'; 3 | export * from './NonDeterministicFiniteAutomaton'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/fa/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": [ 4 | "./libs/**/*.ts" 5 | ], 6 | "compilerOptions": { 7 | "outDir": "./dist", 8 | "rootDir": "./", 9 | }, 10 | } -------------------------------------------------------------------------------- /apps/landing/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import "../../styles/main.css"; 3 | 4 | 5 | const MyApp = ({ Component, pageProps }: AppProps) => ; 6 | 7 | export default MyApp; 8 | -------------------------------------------------------------------------------- /docs/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json", 3 | "include": [ 4 | "index.ts" 5 | ], 6 | "compilerOptions": { 7 | "outDir": "./dist", 8 | "module": "ES2020" 9 | }, 10 | } -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cfg/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": [ 4 | "./libs/**/*.ts" 5 | ], 6 | "compilerOptions": { 7 | "outDir": "./dist", 8 | "rootDir": "./libs", 9 | }, 10 | } -------------------------------------------------------------------------------- /packages/testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": [ 4 | "./libs/**/*.ts" 5 | ], 6 | "compilerOptions": { 7 | "outDir": "./dist", 8 | "rootDir": "./libs", 9 | }, 10 | } -------------------------------------------------------------------------------- /packages/cfg/libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../extractTerminalsFromCfg'; 2 | export * from './generateNewVariable'; 3 | export * from './isAllTerminal'; 4 | export * from './removeProductionRules'; 5 | export * from './setOperations'; 6 | -------------------------------------------------------------------------------- /apps/playground/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import Drawer from "./Drawer"; 2 | 3 | export * from './Button'; 4 | export * from './Flex'; 5 | export * from './Icons'; 6 | export * from './Select'; 7 | export { 8 | Drawer 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /packages/fa/tests/FiniteAutomaton/validate.test.ts: -------------------------------------------------------------------------------- 1 | import { validate } from '../../libs/FiniteAutomaton/validate'; 2 | 3 | it(`Should throw an error`, () => { 4 | expect(() => validate('DFA', ['Error 1'])).toThrow(`Error validating automaton`); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/regex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": [ 4 | "./libs/**/*.ts", 5 | "./examples" 6 | ], 7 | "compilerOptions": { 8 | "outDir": "./dist", 9 | "rootDir": "./", 10 | }, 11 | } -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | padding: 0em 5em; 7 | } 8 | 9 | .featureSvg { 10 | height: 200px; 11 | width: 200px; 12 | } 13 | -------------------------------------------------------------------------------- /apps/landing/postcss.config.js: -------------------------------------------------------------------------------- 1 | // Please do not use the array form (like ['tailwindcss', 'postcss-preset-env']) 2 | // it will create an unexpected error: Invalid PostCSS Plugin found: [0] 3 | 4 | module.exports = { 5 | plugins: { tailwindcss: {}, autoprefixer: {} }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | // Please do not use the array form (like ['tailwindcss', 'postcss-preset-env']) 2 | // it will create an unexpected error: Invalid PostCSS Plugin found: [0] 3 | 4 | module.exports = { 5 | plugins: { tailwindcss: {}, autoprefixer: {} }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/landing/styles/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind utilities; 3 | @tailwind variants; 4 | 5 | body { 6 | font-family: 'Inconsolata', monospace; 7 | height: 100%; 8 | } 9 | 10 | html { 11 | height: 100vh; 12 | } 13 | 14 | #__next { 15 | height: 100%; 16 | } -------------------------------------------------------------------------------- /apps/playground/styles/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind utilities; 3 | @tailwind variants; 4 | 5 | body { 6 | font-family: 'Roboto', monospace; 7 | height: 100%; 8 | } 9 | 10 | html { 11 | height: 100vh; 12 | } 13 | 14 | #__next { 15 | height: 100%; 16 | } -------------------------------------------------------------------------------- /apps/landing/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /packages/cfg/tests/utils/setOperations.test.ts: -------------------------------------------------------------------------------- 1 | import { setCrossProduct } from "../../libs/utils/setOperations" 2 | 3 | describe('setCrossProduct', () => { 4 | it(`Set cross product`, () => { 5 | expect(Array.from(setCrossProduct(new Set(), new Set()))).toStrictEqual([]) 6 | }) 7 | }) -------------------------------------------------------------------------------- /apps/playground/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /scripts/vercel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "VERCEL_ENV: $VERCEL_ENV" 4 | 5 | if [[ "$VERCEL_ENV" == "production" ]] ; then 6 | # Proceed with the build 7 | echo "✅ - Build can proceed" 8 | exit 1; 9 | else 10 | # Don't build for preview 11 | echo "🛑 - Build cancelled" 12 | exit 0; 13 | fi -------------------------------------------------------------------------------- /packages/regex/libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addConcatOperatorToRegex'; 2 | export * from './convertInfixRegexToPostfix'; 3 | export * from './generateEpsilonNfaFromRegex'; 4 | export * from './generateRegexNodeTransitions'; 5 | export * from './generateRegexTree'; 6 | export * from './validateRegex'; 7 | -------------------------------------------------------------------------------- /apps/playground/src/contexts/DrawerContext.ts: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | 3 | interface IDrawerContext { 4 | isDrawerOpen: boolean 5 | setIsDrawerOpen: Dispatch> 6 | } 7 | 8 | export const DrawerContext = React.createContext({} as IDrawerContext); 9 | -------------------------------------------------------------------------------- /apps/playground/src/components/Flex.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { Box } from "@mui/material"; 3 | 4 | export const Flex = styled(Box)` 5 | display: flex; 6 | gap: ${({theme}) => theme.spacing(1)}; 7 | overflow: auto; 8 | ` 9 | 10 | export const FlexCol = styled(Flex)` 11 | flex-direction: column; 12 | `; -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | /docs/**/api 23 | .vercel 24 | -------------------------------------------------------------------------------- /packages/testing/libs/utils/generateRandomNumber.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random number within a min and max limit 3 | * @param min Minimum limit 4 | * @param max Maximum limit 5 | * @returns generated random number 6 | */ 7 | export function generateRandomNumber(min: number, max: number) { 8 | return Math.floor(min + Math.random() * (max + 1 - min)); 9 | } 10 | -------------------------------------------------------------------------------- /packages/testing/libs/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './countFileLines'; 2 | export * from './createFileWriteStreams'; 3 | export * from './generateAggregateMessage'; 4 | export * from './generateCaseMessage'; 5 | export * from './generateRandomLanguage'; 6 | export * from './generateUniversalLanguage'; 7 | export * from './testAutomata'; 8 | export * from './testAutomaton'; 9 | -------------------------------------------------------------------------------- /packages/testing/tests/generateRandomLanguage.test.ts: -------------------------------------------------------------------------------- 1 | import { generateRandomLanguage } from '../libs/utils/generateRandomLanguage'; 2 | 3 | it(`Should generate random unique strings within length`, () => { 4 | const randomStrings = generateRandomLanguage(10, ['a', 'b', 'c'], 5, 5); 5 | expect(randomStrings.length).toBe(10); 6 | expect(new Set(randomStrings).size === 10).toBe(true); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/cfg/tests/utils/isAllTerminal.test.ts: -------------------------------------------------------------------------------- 1 | import { isAllTerminal } from '../../libs/utils/isAllTerminal'; 2 | 3 | it(`Should return true if all is terminal`, () => { 4 | expect(isAllTerminal(['The', 'A', 'An'], 'The A An')).toBe(true); 5 | }); 6 | 7 | it(`Should return false if one is not terminal`, () => { 8 | expect(isAllTerminal(['The', 'A', 'An'], 'The Var An')).toBe(false); 9 | }); 10 | -------------------------------------------------------------------------------- /utils/equivalency.ts: -------------------------------------------------------------------------------- 1 | export function arrayEquivalency(array1: Array, array2: Array) { 2 | return setEquivalency(new Set(array1), new Set(array2)); 3 | } 4 | 5 | export function setEquivalency(set1: Set, set2: Set) { 6 | if (set1.size !== set2.size) { 7 | return false; 8 | } else { 9 | const set1Array = Array.from(set1); 10 | return set1Array.every((array1Element) => set2.has(array1Element)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cfg/tests/setEquivalency.ts: -------------------------------------------------------------------------------- 1 | export function arrayEquivalency(array1: Array, array2: Array) { 2 | return setEquivalency(new Set(array1), new Set(array2)); 3 | } 4 | 5 | export function setEquivalency(set1: Set, set2: Set) { 6 | if (set1.size !== set2.size) { 7 | return false; 8 | } else { 9 | const set1Array = Array.from(set1); 10 | return set1Array.every((array1Element) => set2.has(array1Element)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cfg/libs/utils/isAllTerminal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if all the letters of a word are terminal 3 | * @param terminals Array of terminals 4 | * @param word Word that is required to be check 5 | * @returns True if all the letters of a word are terminals false otherwise 6 | */ 7 | export function isAllTerminal(terminals: string[], word: string) { 8 | const terminalsSet = new Set(terminals); 9 | return word.split(' ').every((letter) => terminalsSet.has(letter)); 10 | } 11 | -------------------------------------------------------------------------------- /packages/fa/libs/FiniteAutomaton/validate.ts: -------------------------------------------------------------------------------- 1 | export function validate(automatonLabel: string, automatonValidationErrors: string[]) { 2 | if (automatonValidationErrors.length !== 0) { 3 | console.log(`${automatonLabel} ${automatonValidationErrors.length.toString()} Errors`); 4 | automatonValidationErrors.forEach((automatonValidationError) => 5 | console.log(`${automatonValidationError}\n`) 6 | ); 7 | throw new Error(`Error validating automaton`); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | .heroBanner { 2 | padding: 4rem 0; 3 | text-align: center; 4 | position: relative; 5 | overflow: hidden; 6 | } 7 | 8 | @media screen and (max-width: 966px) { 9 | .heroBanner { 10 | padding: 2rem; 11 | } 12 | } 13 | 14 | .buttons { 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | } 19 | 20 | .features { 21 | display: flex; 22 | align-items: center; 23 | padding: 2rem 0; 24 | width: 100%; 25 | } 26 | 27 | .featureImage { 28 | height: 200px; 29 | width: 200px; 30 | } -------------------------------------------------------------------------------- /apps/playground/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@mui/material/styles"; 2 | import { AppProps } from 'next/app'; 3 | import "../../styles/main.css"; 4 | import { generateTheme } from '../utils/generateTheme'; 5 | 6 | const generatedTheme = generateTheme(); 7 | 8 | const MyApp = ({ Component, pageProps }: AppProps) => { 9 | return 10 |
11 | 12 |
13 |
14 | }; 15 | 16 | export default MyApp; 17 | -------------------------------------------------------------------------------- /packages/cfg/libs/utils/setOperations.ts: -------------------------------------------------------------------------------- 1 | export function setDifference(setA: Set, setB: Set) { 2 | return new Set(Array.from(setA).filter((setAElement) => !setB.has(setAElement))); 3 | } 4 | 5 | export function setCrossProduct(setA: Set, setB: Set) { 6 | const crossProductSet: Set = new Set(); 7 | if (setA.size === 0 || setB.size === 0) return crossProductSet; 8 | setA.forEach((setAItem) => { 9 | setB.forEach((setBItem) => crossProductSet.add(`${setAItem} ${setBItem}`)); 10 | }); 11 | return crossProductSet; 12 | } 13 | -------------------------------------------------------------------------------- /apps/landing/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next 13 | /out 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | Thumbs.db 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # dotenv local files 29 | .env*.local 30 | 31 | # local folder 32 | local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /apps/playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next 13 | /out 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | Thumbs.db 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # dotenv local files 29 | .env*.local 30 | 31 | # local folder 32 | local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /packages/fa/tests/DeterministicFiniteAutomaton/generateStateGroupsRecord.test.ts: -------------------------------------------------------------------------------- 1 | import { generateStateGroupsRecord } from '../../libs/DeterministicFiniteAutomaton/generateStateGroupsRecord'; 2 | 3 | describe('generateStateGroupsRecord', () => { 4 | it(`Generate state groups record`, () => { 5 | const stateGroupRecords = generateStateGroupsRecord( 6 | ['A', 'B,C', 'E', 'D'], 7 | [['A'], ['B,C', 'E'], ['D']] 8 | ); 9 | expect(stateGroupRecords).toStrictEqual({ 10 | A: 0, 11 | 'B,C': 1, 12 | D: 2, 13 | E: 1, 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/landing/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 3 | enabled: process.env.ANALYZE === 'true', 4 | }); 5 | 6 | module.exports = withBundleAnalyzer({ 7 | poweredByHeader: false, 8 | trailingSlash: true, 9 | basePath: '', 10 | // The starter code load resources from `public` folder with `router.basePath` in React components. 11 | // So, the source code is "basePath-ready". 12 | // You can remove `basePath` if you don't need it. 13 | reactStrictMode: true, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/playground/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 3 | enabled: process.env.ANALYZE === 'true', 4 | }); 5 | 6 | module.exports = withBundleAnalyzer({ 7 | poweredByHeader: false, 8 | trailingSlash: true, 9 | basePath: '', 10 | // The starter code load resources from `public` folder with `router.basePath` in React components. 11 | // So, the source code is "basePath-ready". 12 | // You can remove `basePath` if you don't need it. 13 | reactStrictMode: true, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/playground/src/contexts/CfgContext.ts: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | import { ContextFreeGrammarWithLabel, UserInputGrammar } from '../types'; 3 | 4 | interface ICfgContext { 5 | grammars: ContextFreeGrammarWithLabel[] 6 | currentSelectedGrammar: ContextFreeGrammarWithLabel | null; 7 | setCurrentSelectedGrammar: Dispatch< 8 | SetStateAction 9 | >; 10 | addGrammar: ((userInputGrammar: UserInputGrammar) => void) 11 | } 12 | 13 | export const CfgContext = React.createContext({} as ICfgContext); 14 | -------------------------------------------------------------------------------- /packages/fa/tests/NonDeterministicFiniteAutomaton/epsilonClosureOfState.test.ts: -------------------------------------------------------------------------------- 1 | import { epsilonClosureOfState } from '../../libs/NonDeterministicFiniteAutomaton/epsilonClosureOfState'; 2 | 3 | describe('epsilonClosureOfState', () => { 4 | it(`Epsilon closure of state`, () => { 5 | const epsilonClosuredStates = epsilonClosureOfState( 6 | { 7 | A: ['B'], 8 | B: ['C', 'D'], 9 | }, 10 | 'A' 11 | ); 12 | expect(epsilonClosuredStates).toStrictEqual(['A', 'B', 'C', 'D']); 13 | }); 14 | }); 15 | 16 | it(`Should generate epsilon closure of a state`, () => {}); 17 | -------------------------------------------------------------------------------- /packages/testing/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | return { 3 | rootDir: process.cwd(), 4 | testTimeout: 30000, 5 | testEnvironment: 'node', 6 | verbose: true, 7 | testPathIgnorePatterns: ['/node_modules', '/dist'], 8 | modulePathIgnorePatterns: ['/dist'], 9 | roots: ['/tests'], 10 | testMatch: ['/tests/**/*.test.ts'], 11 | transform: { 12 | '^.+\\.(ts)$': 'ts-jest', 13 | }, 14 | collectCoverageFrom: ['libs/**/*.ts'], 15 | collectCoverage: true, 16 | coverageDirectory: './coverage', 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/cfg/tests/convertGrammarToString.test.ts: -------------------------------------------------------------------------------- 1 | import { convertGrammarToString } from '../libs/convertGrammarToString'; 2 | 3 | describe('convertGrammarToString', () => { 4 | it(`Convert grammar to string`, () => { 5 | const stringifiedGrammar = convertGrammarToString({ 6 | S: ['Adj Noun Verb', 'Adj Verb'], 7 | Noun: ['Sam', 'Alice', ''], 8 | Adj: ['quickly'], 9 | Verb: ['talked'], 10 | }); 11 | expect(stringifiedGrammar).toStrictEqual([ 12 | 'S -> Adj Noun Verb | Adj Verb', 13 | 'Noun -> Sam | Alice | ϵ', 14 | 'Adj -> quickly', 15 | 'Verb -> talked', 16 | ]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /docs/src/theme/IconDarkMode/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export default function IconDarkMode(props) { 3 | return ( 4 | 5 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/playground/src/components/cfg/GrammarString.tsx: -------------------------------------------------------------------------------- 1 | import { convertGrammarToString, IContextFreeGrammar } from "@fauton/cfg"; 2 | 3 | interface GrammarStringProps { 4 | productionRules: IContextFreeGrammar["productionRules"] 5 | } 6 | 7 | export function GrammarString(props: GrammarStringProps) { 8 | const { productionRules } = props; 9 | const grammarStringLines = convertGrammarToString(productionRules); 10 | return
11 | {grammarStringLines.map((grammarStringLine, grammarStringLineIndex) => {grammarStringLine})} 12 |
13 | } -------------------------------------------------------------------------------- /docs/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc-scripts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./dist/index.js", 6 | "exports": "./dist/index.js", 7 | "type": "module", 8 | "scripts": { 9 | "start": "node dist/index.js", 10 | "build": "tsc --sourceMap false --declaration false", 11 | "build:watch": "tsc --sourceMap false --declaration false --watch" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "marked": "^4.0.10" 18 | }, 19 | "devDependencies": { 20 | "@types/marked": "^4.0.1" 21 | }, 22 | "engines": { 23 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 24 | } 25 | } -------------------------------------------------------------------------------- /docs/scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc-scripts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/marked": { 8 | "version": "4.0.1", 9 | "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz", 10 | "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag==", 11 | "dev": true 12 | }, 13 | "marked": { 14 | "version": "4.0.10", 15 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", 16 | "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/cfg/tests/utils/generateNewVariable.test.ts: -------------------------------------------------------------------------------- 1 | import { generateNewVariable } from '../../libs/utils/generateNewVariable'; 2 | 3 | it(`Should generate new variable`, () => { 4 | const newVariable = generateNewVariable(['1']); 5 | expect(newVariable.match(/[A-Z][0-9]/)).toBeTruthy(); 6 | }); 7 | 8 | it(`Should generate different variable if it exists in variables array`, () => { 9 | // Simulating a variable that is already present 10 | jest 11 | .spyOn(Math, 'random') 12 | .mockReturnValueOnce(0) 13 | .mockReturnValueOnce(0) 14 | .mockReturnValueOnce(0.05) 15 | .mockReturnValueOnce(0.1); 16 | const newVariable = generateNewVariable(['A0']); 17 | expect(newVariable).toBe('B1'); 18 | jest.restoreAllMocks(); 19 | }); 20 | -------------------------------------------------------------------------------- /apps/playground/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from 'next/document'; 2 | 3 | // Need to create a custom _document because i18n support is not compatible with `next export`. 4 | const MyDocument = () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default MyDocument; 21 | -------------------------------------------------------------------------------- /packages/cfg/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | return { 3 | rootDir: process.cwd(), 4 | testTimeout: 30000, 5 | testEnvironment: 'node', 6 | verbose: true, 7 | testPathIgnorePatterns: ['/node_modules', '/dist'], 8 | modulePathIgnorePatterns: ['/dist'], 9 | roots: ['/tests'], 10 | testMatch: ['/tests/**/*.test.ts'], 11 | transform: { 12 | '^.+\\.(ts)$': 'ts-jest', 13 | }, 14 | collectCoverageFrom: ['libs/**/*.ts'], 15 | collectCoverage: true, 16 | coverageDirectory: './coverage', 17 | coverageThreshold: { 18 | global: { 19 | branches: 95, 20 | functions: 95, 21 | lines: 95, 22 | statements: -10, 23 | }, 24 | }, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /apps/landing/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Html, Main, NextScript } from 'next/document'; 2 | 3 | // Need to create a custom _document because i18n support is not compatible with `next export`. 4 | const MyDocument = () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default MyDocument; 21 | -------------------------------------------------------------------------------- /packages/cfg/libs/convertGrammarToString.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar } from './types'; 2 | 3 | /** 4 | * Convert a cfg to its string representation 5 | * @param productionRules Production rules record 6 | * @returns A string representation of the cfg production rules 7 | */ 8 | export function convertGrammarToString(productionRules: IContextFreeGrammar['productionRules']) { 9 | const grammarStringLines: string[] = []; 10 | const productionRulesEntries = Object.entries(productionRules); 11 | productionRulesEntries.forEach(([variable, rules]) => { 12 | grammarStringLines.push( 13 | `${variable} -> ${rules.map((rule) => (rule.length === 0 ? 'ϵ' : rule)).join(' | ')}` 14 | ); 15 | }); 16 | return grammarStringLines; 17 | } 18 | -------------------------------------------------------------------------------- /packages/fa/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | return { 3 | rootDir: process.cwd(), 4 | testTimeout: 30000, 5 | testEnvironment: 'node', 6 | verbose: true, 7 | testPathIgnorePatterns: ['/node_modules', '/dist'], 8 | modulePathIgnorePatterns: ['/dist'], 9 | roots: ['/tests'], 10 | testMatch: ['/tests/**/*.test.ts'], 11 | transform: { 12 | '^.+\\.(ts)$': 'ts-jest', 13 | }, 14 | collectCoverageFrom: ['libs/**/*.ts'], 15 | collectCoverage: true, 16 | coverageDirectory: './coverage', 17 | coverageThreshold: { 18 | global: { 19 | branches: 95, 20 | functions: 95, 21 | lines: 95, 22 | statements: -10, 23 | }, 24 | }, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/regex/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | return { 3 | rootDir: process.cwd(), 4 | testTimeout: 30000, 5 | testEnvironment: 'node', 6 | verbose: true, 7 | testPathIgnorePatterns: ['/node_modules', '/dist'], 8 | modulePathIgnorePatterns: ['/dist'], 9 | roots: ['/tests'], 10 | testMatch: ['/tests/**/*.test.ts'], 11 | transform: { 12 | '^.+\\.(ts)$': 'ts-jest', 13 | }, 14 | collectCoverageFrom: ['libs/**/*.ts'], 15 | collectCoverage: true, 16 | coverageDirectory: './coverage', 17 | coverageThreshold: { 18 | global: { 19 | branches: 95, 20 | functions: 95, 21 | lines: 95, 22 | statements: -10, 23 | }, 24 | }, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/fa/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/fa", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "nanoid": { 8 | "version": "2.1.11", 9 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", 10 | "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==" 11 | }, 12 | "shortid": { 13 | "version": "2.2.16", 14 | "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", 15 | "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", 16 | "requires": { 17 | "nanoid": "^2.1.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeEmptyProduction.test.ts: -------------------------------------------------------------------------------- 1 | import { removeEmptyProduction } from '../libs/removeEmptyProduction'; 2 | 3 | describe('removeEmptyProduction', () => { 4 | it(`Remove empty production`, () => { 5 | const productionRules = { 6 | S: ['Verb', '', 'B', 'B Verb', 'A', 'A Verb', 'A B', 'A B Verb'], 7 | A: ['a', 'a A'], 8 | B: ['b', 'b B'], 9 | Verb: [], 10 | }; 11 | const nonEmptyProductionVariables = removeEmptyProduction({ 12 | productionRules, 13 | variables: ['S', 'A', 'B', 'Verb'], 14 | }); 15 | expect(productionRules).toStrictEqual({ 16 | S: ['', 'B', 'A', 'A B'], 17 | A: ['a', 'a A'], 18 | B: ['b', 'b B'], 19 | }); 20 | expect(nonEmptyProductionVariables).toStrictEqual(['S', 'A', 'B']); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/fa/libs/DeterministicFiniteAutomaton/checkEquivalenceBetweenStatesGroups.ts: -------------------------------------------------------------------------------- 1 | export function checkEquivalenceBetweenStatesGroups(statesGroups: [string[][], string[][]]) { 2 | const [statesGroupsOne, statesGroupsTwo] = statesGroups; 3 | if (statesGroupsOne.length !== statesGroupsTwo.length) return false; 4 | 5 | let isEquivalent = true; 6 | const stateGroupsSet: Set = new Set(); 7 | statesGroupsOne.forEach((statesGroup) => { 8 | stateGroupsSet.add(statesGroup.join('')); 9 | }); 10 | for (let index = 0; index < statesGroupsTwo.length; index += 1) { 11 | const statesGroup = statesGroupsTwo[index]; 12 | if (!stateGroupsSet.has(statesGroup.join(''))) { 13 | isEquivalent = false; 14 | break; 15 | } 16 | } 17 | 18 | return isEquivalent; 19 | } 20 | -------------------------------------------------------------------------------- /packages/regex/tests/validateRegex.test.ts: -------------------------------------------------------------------------------- 1 | import { validateRegex } from '../libs/utils/validateRegex'; 2 | 3 | it(`Should validate regex`, () => { 4 | expect(validateRegex('(')).toBe(false); 5 | expect(validateRegex(')')).toBe(false); 6 | expect(validateRegex('()')).toBe(false); 7 | expect(validateRegex('(*')).toBe(false); 8 | expect(validateRegex('*')).toBe(false); 9 | expect(validateRegex('(a**')).toBe(false); 10 | expect(validateRegex('((a|')).toBe(false); 11 | expect(validateRegex('((a|b')).toBe(false); 12 | expect(validateRegex('((a)|b)')).toBe(true); 13 | expect(validateRegex('((a)|(b))')).toBe(true); 14 | expect(validateRegex('((a)|(b)|)')).toBe(false); 15 | expect(validateRegex('a|b')).toBe(true); 16 | expect(validateRegex('(((a)|(b))|(c))')).toBe(true); 17 | }); 18 | -------------------------------------------------------------------------------- /apps/playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | purge: ['./src/**/*.{js,ts,jsx,tsx}'], 4 | darkMode: true, 5 | theme: { 6 | fontSize: { 7 | xs: '0.75rem', 8 | sm: '0.875rem', 9 | base: '1rem', 10 | lg: '1.125rem', 11 | xl: '1.25rem', 12 | '2xl': '1.5rem', 13 | '3xl': '1.875rem', 14 | '4xl': '2.25rem', 15 | '5xl': '3rem', 16 | '6xl': '4rem', 17 | }, 18 | extend: { 19 | colors: { 20 | gray: { 21 | 100: '#f7fafc', 22 | 200: '#edf2f7', 23 | 300: '#e2e8f0', 24 | 400: '#cbd5e0', 25 | 500: '#a0aec0', 26 | 600: '#718096', 27 | 700: '#4a5568', 28 | 800: '#2d3748', 29 | 900: '#1a202c', 30 | }, 31 | }, 32 | }, 33 | }, 34 | variants: {}, 35 | plugins: [], 36 | }; 37 | -------------------------------------------------------------------------------- /packages/testing/libs/utils/countFileLines.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | /** 4 | * Count the number of lines of a given file 5 | * @param filePath Path of the file 6 | * @returns Total lines of a file 7 | */ 8 | export function countFileLines(filePath: string): Promise { 9 | return new Promise((resolve, reject) => { 10 | let lineCount = 0; 11 | fs.createReadStream(filePath) 12 | // On receiving data 13 | .on('data', (buffer) => { 14 | // Loop through the buffer 15 | for (let i = 0; i < buffer.length; i += 1) { 16 | // And check if the buffer item is 10, which indicates a new line 17 | if (buffer[i] === 10) lineCount += 1; 18 | } 19 | }) 20 | .on('end', () => { 21 | resolve(lineCount); 22 | }) 23 | .on('error', reject); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /scripts/uploadCoverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | codecov_file="${GITHUB_WORKSPACE}/scripts/codecov.sh" 4 | 5 | curl -Os https://uploader.codecov.io/latest/linux/codecov > $codecov_file 6 | chmod +x $codecov_file 7 | 8 | packages=( cfg fa testing ) 9 | 10 | RED='\033[0;31m' 11 | GREEN='\033[0;32m' 12 | NC='\033[0m' 13 | 14 | for package in "${packages[@]}" ; do 15 | package_name="@fauton/$package" 16 | file="${GITHUB_WORKSPACE}/packages/$package/coverage/lcov.info" 17 | echo -e "${GREEN}Uploading coverage for package $package_name${NC}" 18 | 19 | if ! ($codecov_file -f $file -F $package -t $CODECOV_TOKEN) then 20 | echo -e "${RED}Error uploading coverage for $package_name${NC}" 21 | exit 1 22 | else 23 | echo -e "${GREEN}Successfully uploaded coverage for $package_name${NC}" 24 | fi 25 | done -------------------------------------------------------------------------------- /apps/playground/src/components/Icons.tsx: -------------------------------------------------------------------------------- 1 | import AddCircleIcon from '@mui/icons-material/AddCircle'; 2 | import MuiDeleteIcon from '@mui/icons-material/Delete'; 3 | import { SvgIconProps } from "@mui/material"; 4 | import { grey } from "@mui/material/colors"; 5 | 6 | interface IconProps extends SvgIconProps{ 7 | size?: number 8 | disabled?: boolean 9 | } 10 | 11 | export function AddIcon(props: IconProps) { 12 | const { disabled, ...rest } = props; 13 | return 16 | } 17 | 18 | export function DeleteIcon(props: IconProps) { 19 | const { disabled, ...rest } = props; 20 | return 23 | 24 | } -------------------------------------------------------------------------------- /packages/cfg/libs/removeUselessProduction.ts: -------------------------------------------------------------------------------- 1 | import { removeNonTerminableProduction } from './removeNonTerminableProduction'; 2 | import { removeUnreachableProduction } from './removeUnreachableProduction'; 3 | import { IContextFreeGrammarInput } from './types'; 4 | import { populateCfg } from './utils/populateCfg'; 5 | 6 | /** 7 | * Reduces an input cfg by removing non terminable and non reachable variables 8 | * @param cfg Variables, start symbol and production rules of a cfg 9 | * @returns An array of terminable and reachable variables 10 | */ 11 | export function removeUselessProduction(inputCfg: IContextFreeGrammarInput) { 12 | const cfg = populateCfg(inputCfg); 13 | const terminableVariables = removeNonTerminableProduction(cfg); 14 | return removeUnreachableProduction({ 15 | ...cfg, 16 | variables: terminableVariables, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/regex/libs/index.ts: -------------------------------------------------------------------------------- 1 | import { IAutomatonTestLogicFn, IRegularExpression } from './types'; 2 | import * as RegularExpressionUtils from './utils'; 3 | 4 | export class RegularExpression { 5 | automaton: IRegularExpression; 6 | 7 | testLogic: IAutomatonTestLogicFn; 8 | 9 | constructor(testLogic: IAutomatonTestLogicFn, automaton: IRegularExpression, flags?: string[]) { 10 | this.automaton = { 11 | ...automaton, 12 | regex: new RegExp(automaton.regex, ...(flags ?? [])), 13 | }; 14 | this.testLogic = testLogic; 15 | } 16 | 17 | test(inputString: string) { 18 | return Boolean(inputString.match(this.automaton.regex)); 19 | } 20 | 21 | convertToPostfix() { 22 | return RegularExpressionUtils.convertInfixRegexToPostfix(String(this.automaton.regex)) 23 | } 24 | } 25 | 26 | export { RegularExpressionUtils }; 27 | 28 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeUnreachableProduction.test.ts: -------------------------------------------------------------------------------- 1 | import { removeUnreachableProduction } from '../libs/removeUnreachableProduction'; 2 | 3 | describe('removeUnreachableProduction', () => { 4 | it(`Remove unreachable production`, () => { 5 | const cfg = { 6 | productionRules: { 7 | Sub: ['Noun', 'Adj Verb'], 8 | Adj: ['an Adj', 'an'], 9 | Verb: ['be Verb', 'be'], 10 | Conj: ['can Conj', 'can'], 11 | }, 12 | startVariable: 'Sub', 13 | variables: ['Sub', 'Adj', 'Verb', 'Conj', 'Noun'], 14 | }; 15 | const reachableVariables = removeUnreachableProduction(cfg); 16 | expect(reachableVariables).toStrictEqual(['Sub', 'Noun', 'Adj', 'Verb']); 17 | expect(cfg.productionRules).toStrictEqual({ 18 | Sub: ['Noun', 'Adj Verb'], 19 | Adj: ['an Adj', 'an'], 20 | Verb: ['be Verb', 'be'], 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /apps/playground/src/types.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar } from '@fauton/cfg'; 2 | import { Theme as MaterialUITheme } from '@mui/material'; 3 | 4 | export type TGrammarOperations = 5 | | 'cnf' 6 | | 'remove_null' 7 | | 'remove_unit' 8 | | 'remove_empty' 9 | | 'remove_useless' 10 | | 'remove_unreachable' 11 | | 'remove_non_terminable'; 12 | 13 | export interface GrammarPipelineStep { 14 | operation: TGrammarOperations; 15 | input: IContextFreeGrammar; 16 | output: IContextFreeGrammar; 17 | } 18 | 19 | export type UserInputGrammar = { 20 | label: string; 21 | rules: Array<{ 22 | variable: string; 23 | substitutions: string[][]; 24 | }>; 25 | }; 26 | 27 | export type ContextFreeGrammarWithLabel = IContextFreeGrammar & {label: string} 28 | 29 | declare module '@emotion/react' { 30 | export interface Theme extends MaterialUITheme {} 31 | } -------------------------------------------------------------------------------- /packages/cfg/tests/generateParseTreeFromDerivations.test.ts: -------------------------------------------------------------------------------- 1 | import { generateParseTreeFromDerivations } from "../libs/generateParseTreeFromDerivations"; 2 | 3 | describe('generateParseTreeFromDerivations', () => { 4 | it(`Parse tree from derivations`, () => { 5 | const parseTree = generateParseTreeFromDerivations(["S", "A"], [ 6 | ["S", ["A", "A"]], 7 | ["A", ["a", "A"]], 8 | ["A", ["b"]], 9 | ["A", ["a", "A"]], 10 | ["A", ["b"]] 11 | ]) 12 | expect(parseTree).toStrictEqual({ 13 | S: [ 14 | { 15 | "A": [ 16 | "a", 17 | { 18 | "A": ["b"] 19 | } 20 | ] 21 | }, 22 | { 23 | "A": [ 24 | "a", 25 | { 26 | "A": ["b"] 27 | } 28 | ] 29 | } 30 | ] 31 | }) 32 | }) 33 | }) -------------------------------------------------------------------------------- /packages/cfg/libs/simplifyCfg.ts: -------------------------------------------------------------------------------- 1 | import { removeEmptyProduction } from './removeEmptyProduction'; 2 | import { removeNullProduction } from './removeNullProduction'; 3 | import { removeUnitProduction } from './removeUnitProduction'; 4 | import { removeUselessProduction } from './removeUselessProduction'; 5 | import { IContextFreeGrammarInput } from './types'; 6 | import { populateCfg } from './utils/populateCfg'; 7 | 8 | /** 9 | * 10 | * @param inputCfg Input cfg to simplify 11 | * @returns 12 | */ 13 | export function simplifyCfg(inputCfg: IContextFreeGrammarInput) { 14 | const cfg = populateCfg(inputCfg); 15 | removeNullProduction(cfg); 16 | removeUnitProduction(cfg); 17 | const reducedVariables = removeUselessProduction(cfg); 18 | cfg.variables = reducedVariables; 19 | const nonEmptyVariables = removeEmptyProduction(cfg); 20 | cfg.variables = nonEmptyVariables; 21 | } 22 | -------------------------------------------------------------------------------- /apps/playground/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/dist/client/router"; 2 | import Head from "next/head"; 3 | import { Button } from "../components"; 4 | 5 | const Index = () => { 6 | const router = useRouter(); 7 | return <> 8 | 9 | Fauton Playground 10 | 11 |
12 |
25 | ; 26 | }; 27 | 28 | export default Index; -------------------------------------------------------------------------------- /packages/cfg/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/cfg", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@datastructures-js/binary-search-tree": { 8 | "version": "4.3.1", 9 | "resolved": "https://registry.npmjs.org/@datastructures-js/binary-search-tree/-/binary-search-tree-4.3.1.tgz", 10 | "integrity": "sha512-YmxFM5zBz3E9vCnXUidDgOgUaWNqYBqjJp9EkAc3dInpNcq5qutVfJJxI3P4RDUl9DV6wzADN7shqIA1YQI93g==" 11 | }, 12 | "@datastructures-js/linked-list": { 13 | "version": "5.1.1", 14 | "resolved": "https://registry.npmjs.org/@datastructures-js/linked-list/-/linked-list-5.1.1.tgz", 15 | "integrity": "sha512-fj5vOozZX5Sw+Z7dJcicvQxMbWTuOizZ+dW+pMcYWMMgMiLERzJ3Zv6Tdn+VStej9ax6tq7LIQxKGiZyXMvWmg==", 16 | "requires": { 17 | "@datastructures-js/binary-search-tree": "^4.1.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/scripts/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { extractExamples, generateExamples } from 'typedoc-example-generator'; 4 | 5 | const packages = ['cfg']; 6 | 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | async function main() { 10 | for (let index = 0; index < packages.length; index++) { 11 | const packageName = packages[index]; 12 | const modulesMarkdownFilePath = path.resolve( 13 | __dirname, 14 | `../../docs/fauton-${packageName}/modules.md` 15 | ); 16 | const testFilesDirectory = path.resolve(__dirname, `../../../packages/${packageName}/tests`); 17 | const functionExamplesRecord = await extractExamples(testFilesDirectory); 18 | await generateExamples( 19 | modulesMarkdownFilePath, 20 | functionExamplesRecord, 21 | `@fauton/${packageName}` 22 | ); 23 | } 24 | } 25 | 26 | main(); 27 | -------------------------------------------------------------------------------- /packages/cfg/libs/utils/populateCfg.ts: -------------------------------------------------------------------------------- 1 | import { extractTerminalsFromCfg } from '../extractTerminalsFromCfg'; 2 | import { IContextFreeGrammar, IContextFreeGrammarInput } from '../types'; 3 | 4 | /** 5 | * Populates the variables and terminals of cfg via extraction 6 | * @param cfg Cfg to populate 7 | * @returns Populated cfg 8 | */ 9 | export function populateCfg(cfg: IContextFreeGrammarInput) { 10 | // TODO: Loop through all the production rules and extract variables from them 11 | if (!cfg.variables) { 12 | cfg.variables = Object.keys(cfg.productionRules); 13 | } 14 | if (!cfg.startVariable) { 15 | // eslint-disable-next-line 16 | cfg.startVariable = cfg.variables[0]; 17 | } 18 | 19 | if (!cfg.terminals) { 20 | cfg.terminals = extractTerminalsFromCfg({ 21 | productionRules: cfg.productionRules, 22 | variables: cfg.variables, 23 | }); 24 | } 25 | 26 | return cfg as IContextFreeGrammar; 27 | } 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "strict": false, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "declaration": true, 12 | "moduleResolution": "node", 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "allowSyntheticDefaultImports": true, 22 | "esModuleInterop": true, 23 | "emitDecoratorMetadata": true, 24 | "experimentalDecorators": true, 25 | "resolveJsonModule": true, 26 | "incremental": false, 27 | "baseUrl": "./", 28 | "watch": false, 29 | "types": [ 30 | "node", 31 | "jest" 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /packages/fa/readme.md: -------------------------------------------------------------------------------- 1 | # `@fauton/fa` 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 |

11 | 12 |

Github |  13 | Docs |  14 | NPM 15 |

16 | 17 |

A package to work with finite automata

18 | -------------------------------------------------------------------------------- /packages/fa/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb-base', 'prettier', 'plugin:import/recommended', 'plugin:import/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | ignorePatterns: ['dist', 'tests', 'examples', 'experiment'], 11 | parserOptions: { 12 | project: path.join(__dirname, './tsconfig.json'), 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'prettier', 'import'], 17 | rules: { 18 | 'import/extensions': [ 19 | 'error', 20 | 'ignorePackages', 21 | { 22 | js: 'never', 23 | jsx: 'never', 24 | ts: 'never', 25 | tsx: 'never', 26 | }, 27 | ], 28 | 'no-await-in-loop': 'off', 29 | 'import/prefer-default-export': 'off', 30 | 'no-else-return': 'off', 31 | 'one-var': 'off', 32 | 'no-console': 'off', 33 | 'no-bitwise': 'off', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/regex/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb-base', 'prettier', 'plugin:import/recommended', 'plugin:import/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | ignorePatterns: ['dist', 'tests', 'examples', 'experiment'], 11 | parserOptions: { 12 | project: path.join(__dirname, './tsconfig.json'), 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'prettier', 'import'], 17 | rules: { 18 | 'import/extensions': [ 19 | 'error', 20 | 'ignorePackages', 21 | { 22 | js: 'never', 23 | jsx: 'never', 24 | ts: 'never', 25 | tsx: 'never', 26 | }, 27 | ], 28 | 'no-await-in-loop': 'off', 29 | 'import/prefer-default-export': 'off', 30 | 'no-else-return': 'off', 31 | 'one-var': 'off', 32 | 'no-console': 'off', 33 | 'no-bitwise': 'off', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/testing/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb-base', 'prettier', 'plugin:import/recommended', 'plugin:import/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | ignorePatterns: ['dist', 'tests', 'examples', 'experiment'], 11 | parserOptions: { 12 | project: path.join(__dirname, './tsconfig.json'), 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'prettier', 'import'], 17 | rules: { 18 | 'import/extensions': [ 19 | 'error', 20 | 'ignorePackages', 21 | { 22 | js: 'never', 23 | jsx: 'never', 24 | ts: 'never', 25 | tsx: 'never', 26 | }, 27 | ], 28 | 'no-await-in-loop': 'off', 29 | 'import/prefer-default-export': 'off', 30 | 'no-else-return': 'off', 31 | 'one-var': 'off', 32 | 'no-console': 'off', 33 | 'no-bitwise': 'off', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/cfg/readme.md: -------------------------------------------------------------------------------- 1 | # `@fauton/cfg` 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 |

11 | 12 |

Github |  13 | Docs |  14 | NPM 15 |

16 | 17 |

A package to work with context free grammars

18 | -------------------------------------------------------------------------------- /packages/regex/tests/generateRegexTree.test.ts: -------------------------------------------------------------------------------- 1 | import { addConcatOperatorToRegex } from '../libs/utils/addConcatOperatorToRegex'; 2 | import { convertInfixRegexToPostfix } from '../libs/utils/convertInfixRegexToPostfix'; 3 | import { generateRegexTree } from '../libs/utils/generateRegexTree'; 4 | 5 | describe('generateRegexTree', () => { 6 | it(`Simple regex`, () => { 7 | const generatedRegexTree = generateRegexTree( 8 | convertInfixRegexToPostfix(addConcatOperatorToRegex('a|b?c*')) 9 | ); 10 | expect(generatedRegexTree).toStrictEqual([ 11 | { 12 | operands: [ 13 | { operands: ['a'], operator: 'Literal' }, 14 | { 15 | operands: [ 16 | { operands: [{ operands: ['b'], operator: 'Literal' }], operator: 'Optional' }, 17 | { operands: [{ operands: ['c'], operator: 'Literal' }], operator: 'Kleene' }, 18 | ], 19 | operator: 'Concat', 20 | }, 21 | ], 22 | operator: 'Or', 23 | }, 24 | '', 25 | ]); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /apps/playground/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { Typography } from "@mui/material"; 3 | import MuiButton from "@mui/material/Button"; 4 | import { MouseEventHandler } from "react"; 5 | 6 | const StyledMuiButton = styled(MuiButton)` 7 | text-transform: capitalize; 8 | text-align: center; 9 | shadow: ${({theme}) => theme.shadows[1]}; 10 | cursor: pointer; 11 | padding: ${({theme}) => theme.spacing(1, 2)}; 12 | width: fit-content; 13 | border-radius: ${({theme}) => theme.spacing(0.5)}; 14 | `; 15 | 16 | export function Button(props: { disabled?: boolean, className?: string, label: string, onClick?: MouseEventHandler }) { 17 | const { onClick, label, className = "", disabled = false } = props; 18 | return 19 | 20 | {label} 21 | 22 | 23 | } -------------------------------------------------------------------------------- /apps/landing/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import router from "next/router"; 2 | import { MouseEventHandler } from "react"; 3 | 4 | function Button(props: { className?: string, label: string, onClick?: MouseEventHandler }) { 5 | const { onClick, label, className = "" } = props; 6 | return
7 | {label} 8 |
9 | } 10 | 11 | const Index = () => { 12 | return
13 |
14 | Welcome to fauton 15 |
16 |
17 |
20 |
; 21 | }; 22 | 23 | export default Index; -------------------------------------------------------------------------------- /packages/cfg/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb-base', 'prettier', 'plugin:import/recommended', 'plugin:import/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | ignorePatterns: ['dist', 'tests', 'examples', 'experiment'], 11 | parserOptions: { 12 | project: path.join(__dirname, './tsconfig.json'), 13 | ecmaVersion: 12, 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'prettier', 'import'], 17 | rules: { 18 | 'import/extensions': [ 19 | 'error', 20 | 'ignorePackages', 21 | { 22 | js: 'never', 23 | jsx: 'never', 24 | ts: 'never', 25 | tsx: 'never', 26 | }, 27 | ], 28 | 'no-await-in-loop': 'off', 29 | 'import/prefer-default-export': 'off', 30 | 'no-else-return': 'off', 31 | 'one-var': 'off', 32 | 'no-console': 'off', 33 | 'no-bitwise': 'off', 34 | 'no-param-reassign': 'off', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/fa/libs/DeterministicFiniteAutomaton/generateStateGroupsRecord.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a record that maps each input states, to its index in the statesGroups 3 | * @param states An array of states of the automaton 4 | * @param statesGroups Array of state groups 5 | * @returns A record which maps each state to its state group index 6 | */ 7 | export function generateStateGroupsRecord(states: string[], statesGroups: string[][]) { 8 | const statesGroupsRecord: Record = {}; 9 | const allStatesSet: Set = new Set(states); 10 | statesGroups.forEach((statesGroup, statesGroupIndex) => { 11 | // State group would contain combinations of states 12 | statesGroup.forEach((state) => { 13 | // Since states could be joined by , or other separators 14 | // We should check whether the character actually is a state 15 | if (allStatesSet.has(state)) { 16 | statesGroupsRecord[state] = statesGroupIndex; 17 | } 18 | }); 19 | }); 20 | return statesGroupsRecord; 21 | } 22 | -------------------------------------------------------------------------------- /packages/cfg/tests/parseWithLL1Table.test.ts: -------------------------------------------------------------------------------- 1 | import { parseWithLL1Table } from "../libs/parseWithLL1Table"; 2 | 3 | describe('parseWithLL1Table', () => { 4 | it(`Parsable string`, () => { 5 | const {parsed, derivations} = parseWithLL1Table({ 6 | productionRules: { 7 | S: ["A A"], 8 | A: ["a A", "b"], 9 | } 10 | }, "abab") 11 | expect(derivations).toStrictEqual([ 12 | ["S", ["A", "A"]], 13 | ["A", ["a", "A"]], 14 | ["A", ["b"]], 15 | ["A", ["a", "A"]], 16 | ["A", ["b"]] 17 | ]) 18 | expect(parsed).toBe(true) 19 | }) 20 | 21 | it(`Unparsable string`, () => { 22 | const {parsed, derivations} = parseWithLL1Table({ 23 | productionRules: { 24 | S: ["A A"], 25 | A: ["a A", "b"], 26 | } 27 | }, "abcab") 28 | expect(derivations).toStrictEqual([ 29 | ["S", ["A", "A"]], 30 | ["A", ["a", "A"]], 31 | ["A", ["b"]], 32 | ]) 33 | expect(parsed).toBe(false) 34 | }) 35 | }) -------------------------------------------------------------------------------- /packages/fa/tests/NonDeterministicFiniteAutomaton/convertToRegularNfa.test.ts: -------------------------------------------------------------------------------- 1 | import { convertToRegularNfa } from '../../libs/NonDeterministicFiniteAutomaton/convertToRegularNfa'; 2 | 3 | describe('convertToRegularNfa', () => { 4 | it(`Converting e-nfa to nfa`, () => { 5 | const transitions = { 6 | A: { 7 | 0: ['A'], 8 | }, 9 | B: { 10 | 1: ['B'], 11 | }, 12 | C: { 13 | 0: ['C'], 14 | 1: ['C'], 15 | }, 16 | }; 17 | convertToRegularNfa({ 18 | alphabets: ['0', '1'], 19 | epsilon_transitions: { 20 | A: ['B'], 21 | B: ['C'], 22 | D: ['C'], 23 | }, 24 | states: ['A', 'B', 'C', 'D'], 25 | transitions, 26 | }); 27 | expect(transitions).toStrictEqual({ 28 | A: { 29 | 0: ['A', 'C', 'B'], 30 | 1: ['B', 'C'], 31 | }, 32 | B: { 33 | 0: ['C'], 34 | 1: ['B', 'C'], 35 | }, 36 | C: { 37 | 0: ['C'], 38 | 1: ['C'], 39 | }, 40 | D: { 41 | 0: ['C'], 42 | 1: ['C'], 43 | }, 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/cfg/tests/convertStringToGrammar.test.ts: -------------------------------------------------------------------------------- 1 | import { convertStringToGrammar } from '../libs/convertStringToGrammar'; 2 | 3 | describe('convertStringToGrammar', () => { 4 | it(`Convert string to grammar`, () => { 5 | const generatedGrammar = convertStringToGrammar( 6 | `S -> Noun Article Verb | Noun Adj Verb\nNoun -> Sam | Bob | Alice\nArticle -> A | The | An\nAdj -> quickly | swiftly\nVerb -> ran | ate\nVerb -> walked` 7 | ); 8 | expect(generatedGrammar).toStrictEqual({ 9 | productionRules: { 10 | S: ['Noun Article Verb', 'Noun Adj Verb'], 11 | Noun: ['Sam', 'Bob', 'Alice'], 12 | Article: ['A', 'The', 'An'], 13 | Adj: ['quickly', 'swiftly'], 14 | Verb: ['ran', 'ate', 'walked'], 15 | }, 16 | variables: ['S', 'Noun', 'Article', 'Adj', 'Verb'], 17 | terminals: [ 18 | 'Sam', 19 | 'Bob', 20 | 'Alice', 21 | 'A', 22 | 'The', 23 | 'An', 24 | 'quickly', 25 | 'swiftly', 26 | 'ran', 27 | 'ate', 28 | 'walked', 29 | ], 30 | startVariable: 'S', 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/fa/libs/NonDeterministicFiniteAutomaton/epsilonClosureOfState.ts: -------------------------------------------------------------------------------- 1 | import { TransformedFiniteAutomaton } from '../types'; 2 | 3 | /** 4 | * Returns an array of states that can be reached from a given state for epsilon symbol 5 | * @param epsilonTransitions Epsilon transitions record of automaton 6 | * @param state Source state 7 | * @returns Array of states 8 | */ 9 | export function epsilonClosureOfState( 10 | epsilonTransitions: TransformedFiniteAutomaton['epsilon_transitions'], 11 | state: string 12 | ) { 13 | const statesStack: string[] = []; 14 | const allEpsilonStates: Set = new Set([state]); 15 | statesStack.push(state); 16 | while (statesStack.length !== 0) { 17 | const currentState = statesStack.pop()!; 18 | epsilonTransitions![currentState]?.forEach((epsilonTransitionState) => { 19 | if (!allEpsilonStates.has(epsilonTransitionState)) { 20 | statesStack.push(epsilonTransitionState); 21 | allEpsilonStates.add(epsilonTransitionState); 22 | } 23 | }); 24 | } 25 | 26 | return Array.from(allEpsilonStates); 27 | } 28 | -------------------------------------------------------------------------------- /packages/cfg/libs/removeEmptyProduction.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammarInput } from './types'; 2 | import { populateCfg } from './utils/populateCfg'; 3 | import { removeProductionRules } from './utils/removeProductionRules'; 4 | 5 | /** 6 | * Removes productions that has no rules and updates rules to remove those rules that references empty production variables 7 | * @param cfg Variables array and production rules record of cfg 8 | * @returns New production rules and variables without empty rule variables 9 | */ 10 | export function removeEmptyProduction( 11 | inputCfg: Pick 12 | ) { 13 | const cfg = populateCfg(inputCfg); 14 | const { productionRules, variables } = cfg; 15 | // Filtering all the variables which dont have any production rules 16 | const emptyProductionVariables = variables.filter( 17 | (variable) => productionRules[variable].length === 0 18 | ); 19 | return removeProductionRules({ 20 | productionRules, 21 | removedVariables: emptyProductionVariables, 22 | variables, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /packages/testing/readme.md: -------------------------------------------------------------------------------- 1 | # `@fauton/testing` 2 | 3 |

4 |

@fauton/testing
5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 |

Github |  14 | Docs |  15 | NPM 16 |

17 | 18 |

A package to test your automaton (regex/dfa/nfa/e-nfa/cfg)

19 | -------------------------------------------------------------------------------- /packages/fa/tests/NonDeterministicFiniteAutomaton/moveAndEpsilonClosureStateSet.test.ts: -------------------------------------------------------------------------------- 1 | import { moveAndEpsilonClosureStateSet } from '../../libs/NonDeterministicFiniteAutomaton/moveAndEpsilonClosureStateSet'; 2 | 3 | describe('moveAndEpsilonClosureStateSet', () => { 4 | it(`Without epsilon transitions record`, () => { 5 | const epsilonClosuredStates = moveAndEpsilonClosureStateSet( 6 | { 7 | A: { 8 | 0: ['B', 'C'], 9 | }, 10 | B: { 11 | 0: ['C'], 12 | }, 13 | }, 14 | null, 15 | ['A', 'B'], 16 | '0' 17 | ); 18 | expect(epsilonClosuredStates).toStrictEqual(['B', 'C']); 19 | }); 20 | 21 | it(`With epsilon transitions record`, () => { 22 | const epsilonClosuredStates = moveAndEpsilonClosureStateSet( 23 | { 24 | A: { 25 | 0: ['B', 'C'], 26 | }, 27 | B: { 28 | 0: ['C'], 29 | }, 30 | }, 31 | { 32 | B: ['A', 'E'], 33 | C: ['D'], 34 | }, 35 | ['A', 'B'], 36 | '0' 37 | ); 38 | expect(epsilonClosuredStates).toStrictEqual(['B', 'C', 'A', 'E', 'D']); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/testing/tests/generateAggregateMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { generateAggregateMessage } from '../libs/utils/generateAggregateMessage'; 2 | 3 | // To cover case where description is present and label is not present 4 | generateAggregateMessage(undefined, 'dfa description', { 5 | falseNegatives: 5, 6 | falsePositives: 10, 7 | trueNegatives: 50, 8 | truePositives: 25, 9 | }); 10 | 11 | describe('generateAggregateMessage', () => { 12 | it(`Generate correct aggregate message values`, () => { 13 | const { values } = generateAggregateMessage('dfa', undefined, { 14 | falseNegatives: 5, 15 | falsePositives: 10, 16 | trueNegatives: 50, 17 | truePositives: 25, 18 | }); 19 | expect(values).toStrictEqual({ 20 | totalCorrect: 75, 21 | totalIncorrect: 15, 22 | totalStrings: 90, 23 | correctPercentage: 83.33333, 24 | incorrectPercentage: 16.66667, 25 | falsePositivesPercentage: 11.11111, 26 | falseNegativesPercentage: 5.55556, 27 | truePositivesPercentage: 27.77778, 28 | trueNegativesPercentage: 55.55556, 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Lint, Build and Test 2 | 3 | on: 4 | push: 5 | branches: ["dev"] 6 | pull_request: 7 | branches: ["dev"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: Lint, Build and Test packages 23 | run: | 24 | # Install shared dependencies 25 | npm install 26 | npm run bootstrap 27 | npm run lint 28 | npm run build 29 | npm run test 30 | - name: Upload test coverage 31 | run: | 32 | upload_coverage_script="${GITHUB_WORKSPACE}/scripts/uploadCoverage.sh" 33 | chmod +x $upload_coverage_script 34 | bash $upload_coverage_script 35 | env: 36 | CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} 37 | -------------------------------------------------------------------------------- /packages/cfg/tests/extractTerminalsFromCfg.test.ts: -------------------------------------------------------------------------------- 1 | import { extractTerminalsFromCfg } from '../libs/extractTerminalsFromCfg'; 2 | 3 | describe('extractTerminalsFromCfg', () => { 4 | it(`Extract terminals from cfg when variables is present`, () => { 5 | const extractedTerminals = extractTerminalsFromCfg({ 6 | productionRules: { 7 | S: ['Noun Verb Adj'], 8 | Verb: ['walk', 'talk'], 9 | Noun: ['Sam', 'Alice'], 10 | Adj: ['quickly'], 11 | }, 12 | variables: ['S', 'Verb', 'Noun', 'Adj'], 13 | }); 14 | expect(extractedTerminals).toStrictEqual(['walk', 'talk', 'Sam', 'Alice', 'quickly']); 15 | }); 16 | 17 | it(`Extract terminals from cfg when variables is not present`, () => { 18 | const extractedTerminals = extractTerminalsFromCfg({ 19 | productionRules: { 20 | S: ['Noun Verb Adj'], 21 | Verb: ['walk', 'talk'], 22 | Noun: ['Sam', 'Alice'], 23 | Adj: ['quickly'], 24 | }, 25 | }); 26 | 27 | expect( 28 | extractedTerminals 29 | ).toStrictEqual(['walk', 'talk', 'Sam', 'Alice', 'quickly']); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/cfg/tests/utils/populateCfg.test.ts: -------------------------------------------------------------------------------- 1 | import { populateCfg } from '../../libs/utils/populateCfg'; 2 | 3 | it(`Should populate cfg, when variables, startVariable and terminals is not present`, () => { 4 | expect( 5 | populateCfg({ 6 | productionRules: { 7 | A: ['An'], 8 | B: ['A', 'B', 'The'], 9 | }, 10 | }) 11 | ).toStrictEqual({ 12 | productionRules: { 13 | A: ['An'], 14 | B: ['A', 'B', 'The'], 15 | }, 16 | variables: ['A', 'B'], 17 | terminals: ['An', 'The'], 18 | startVariable: 'A', 19 | }); 20 | }); 21 | 22 | it(`Should not do anything when variables, startVariable and terminals is present`, () => { 23 | expect( 24 | populateCfg({ 25 | productionRules: { 26 | A: ['An'], 27 | B: ['A', 'B', 'The'], 28 | }, 29 | variables: ['A', 'B'], 30 | terminals: ['An', 'The'], 31 | startVariable: 'A', 32 | }) 33 | ).toStrictEqual({ 34 | productionRules: { 35 | A: ['An'], 36 | B: ['A', 'B', 'The'], 37 | }, 38 | variables: ['A', 'B'], 39 | terminals: ['An', 'The'], 40 | startVariable: 'A', 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/cfg/libs/types.ts: -------------------------------------------------------------------------------- 1 | export interface IContextFreeGrammar { 2 | variables: string[]; 3 | terminals: string[]; 4 | productionRules: Record; 5 | startVariable: string; 6 | } 7 | 8 | // Cfg input might not have all the properties, and they could be null as well 9 | export interface IContextFreeGrammarInput { 10 | variables?: string[] | null; 11 | terminals?: string[] | null; 12 | productionRules: Record; 13 | startVariable?: string; 14 | } 15 | 16 | // eslint-disable-next-line 17 | export type LanguageChecker = (inputString: string) => boolean; 18 | 19 | export interface ICfgLanguageGenerationOption { 20 | minTokenLength: number; 21 | maxTokenLength: number; 22 | skipSimplification?: boolean; 23 | skipValidation?: boolean; 24 | generateTerminals?: boolean; 25 | generateVariables?: boolean; 26 | autoCapitalizeFirstToken?: boolean; 27 | useSpaceWhenJoiningTokens?: boolean; 28 | parseDirection?: 'left' | 'right'; 29 | } 30 | 31 | export type ParseTree = { 32 | [k: string]: (string | ParseTree)[] 33 | } 34 | 35 | export type Derivation = [string, string[]] -------------------------------------------------------------------------------- /packages/regex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/regex", 3 | "version": "0.0.1", 4 | "description": "A package to work with regex validation and parsing", 5 | "main": "dist/libs/index.js", 6 | "typings": "dist/libs/index.d.ts", 7 | "files": [ 8 | "dist/libs" 9 | ], 10 | "scripts": { 11 | "prebuild": "del-cli ./dist", 12 | "prepublishOnly": "npm run lint && npm run build && npm run test", 13 | "build": "tsc --sourceMap false", 14 | "build:watch": "tsc -w", 15 | "lint": "npx eslint \"./libs\" --ext tsx,ts", 16 | "lint:fix": "npx eslint \"./libs\" --ext tsx,ts --fix", 17 | "format": "npx prettier ./libs/**/*.{ts,js} --write", 18 | "test": "npx jest --runInBand" 19 | }, 20 | "keywords": [ 21 | "regex", 22 | "regex-parsing", 23 | "regex-validation" 24 | ], 25 | "author": "Safwan Shaheer ", 26 | "license": "MIT", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/Devorein/fauton.git" 30 | }, 31 | "publishConfig": { 32 | "access": "public" 33 | }, 34 | "dependencies": {}, 35 | "devDependencies": {} 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fauton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/docs/cfg/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: 'cfg' 3 | title: '@fauton/cfg' 4 | slug: '/cfg/' 5 | sidebar_position: 0 6 | custom_edit_url: null 7 | --- 8 | 9 |

10 |

@fauton/cfg
11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 |

Github |  20 | Docs |  21 | NPM 22 |

23 | 24 |

A package to work with context free grammars

25 | -------------------------------------------------------------------------------- /docs/docs/fa/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: 'fa' 3 | title: '@fauton/fa' 4 | slug: '/fa/' 5 | sidebar_position: 0 6 | custom_edit_url: null 7 | --- 8 | 9 | # `@fauton/fa` 10 | 11 |

12 |

@fauton/fa
13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 |

Github |  22 | Docs |  23 | NPM 24 |

25 | 26 |

A package to work with finite automata

27 | -------------------------------------------------------------------------------- /packages/testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/testing", 3 | "version": "0.0.1", 4 | "description": "A package to test your automaton (regex/dfa/nfa/e-nfa/cfg)", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "prebuild": "del-cli ./dist", 12 | "prepublishOnly": "npm run lint && npm run build && npm run test", 13 | "build": "tsc --sourceMap false", 14 | "build:watch": "tsc -w", 15 | "lint": "npx eslint \"./libs\" --ext tsx,ts", 16 | "lint:fix": "npx eslint \"./libs\" --ext tsx,ts --fix", 17 | "format": "npx prettier ./libs/**/*.{ts,js} --write", 18 | "test": "npx jest --runInBand" 19 | }, 20 | "keywords": [ 21 | "automaton-testing" 22 | ], 23 | "author": "Safwan Shaheer ", 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/Devorein/fauton.git" 28 | }, 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "dependencies": { 33 | "cli-progress": "^3.9.1", 34 | "colors": "^1.4.0" 35 | }, 36 | "devDependencies": {} 37 | } -------------------------------------------------------------------------------- /packages/regex/tests/convertInfixRegexToPostfix.test.ts: -------------------------------------------------------------------------------- 1 | import { convertInfixRegexToPostfix } from "../libs/utils/convertInfixRegexToPostfix"; 2 | 3 | describe('convertInfixRegexToPostfix', () => { 4 | it(`Work for single operator regex without parenthesis`, () => { 5 | const postfixRegexString = convertInfixRegexToPostfix("a|b"); 6 | expect(postfixRegexString).toBe("ab|") 7 | }) 8 | 9 | it(`Work for multi operator regex without parenthesis`, () => { 10 | const postfixRegexString1 = convertInfixRegexToPostfix("a?.b"); 11 | const postfixRegexString2 = convertInfixRegexToPostfix("a?+b"); 12 | expect(postfixRegexString1).toBe("a?b.") 13 | expect(postfixRegexString2).toBe("ab+?") 14 | }) 15 | 16 | it(`Work for multi operator regex with parenthesis`, () => { 17 | const postfixRegexString1 = convertInfixRegexToPostfix("(a?.b)"); 18 | expect(postfixRegexString1).toBe("a?b.") 19 | }) 20 | 21 | it(`Work for multi operator regex with mismatched parenthesis`, () => { 22 | const postfixRegexString1 = convertInfixRegexToPostfix("(a?.b"); 23 | expect(postfixRegexString1).toBe("a?b.") 24 | }) 25 | }) -------------------------------------------------------------------------------- /packages/cfg/libs/extractTerminalsFromCfg.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammarInput } from './types'; 2 | 3 | /** 4 | * Extract terminals from cfg rules 5 | * @param inputCfg input Cfg 6 | * @returns An array of terminals 7 | */ 8 | export function extractTerminalsFromCfg( 9 | inputCfg: Omit 10 | ) { 11 | if (!inputCfg.variables) { 12 | inputCfg.variables = Object.keys(inputCfg.productionRules); 13 | } 14 | const terminals: string[] = []; 15 | // Creating a set of variables initially to improve search performance 16 | const variablesSet = new Set(inputCfg.variables); 17 | 18 | Object.values(inputCfg.productionRules).forEach((rules) => { 19 | rules.forEach((rule) => { 20 | const tokens = rule.split(' '); 21 | // Loop through each of the tokens to see which of them are terminals 22 | tokens.forEach((token) => { 23 | // If the token is not a variable and not empty string, its a terminal 24 | if (!variablesSet.has(token) && token) { 25 | terminals.push(token); 26 | } 27 | }); 28 | }); 29 | }); 30 | return Array.from(new Set(terminals)); 31 | } 32 | -------------------------------------------------------------------------------- /apps/landing/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Remi W. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/playground/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Remi W. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/docs/testing/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: 'testing' 3 | title: '@fauton/testing' 4 | slug: '/testing/' 5 | sidebar_position: 0 6 | custom_edit_url: null 7 | --- 8 | 9 | # `@fauton/testing` 10 | 11 |

12 |

@fauton/testing
13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 |

Github |  22 | Docs |  23 | NPM 24 |

25 | 26 |

A package to test your automaton (regex/dfa/nfa/e-nfa/cfg)

27 | -------------------------------------------------------------------------------- /packages/cfg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/cfg", 3 | "version": "0.0.10", 4 | "description": "A package to work with context free grammars and LL1 parsers", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist/*" 9 | ], 10 | "scripts": { 11 | "prebuild": "del-cli ./dist", 12 | "prepublishOnly": "npm run lint && npm run build && npm run test", 13 | "build": "tsc --sourceMap false", 14 | "build:watch": "tsc -w", 15 | "lint": "eslint \"./libs/**/*.{tsx,ts}\"", 16 | "lint:fix": "eslint \"./libs/**/*.{tsx,ts}\"", 17 | "format": "prettier ./libs/**/*.{ts,js} --write", 18 | "test": "jest --runInBand" 19 | }, 20 | "keywords": [ 21 | "context-free-grammar", 22 | "context-free-language", 23 | "cyk-parse", 24 | "cnf", 25 | "ll1-parse-table" 26 | ], 27 | "author": "Safwan Shaheer ", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/Devorein/fauton.git" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | }, 36 | "dependencies": { 37 | "@datastructures-js/linked-list": "^5.1.1" 38 | }, 39 | "devDependencies": {} 40 | } -------------------------------------------------------------------------------- /apps/landing/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | purge: ['./src/**/*.{js,ts,jsx,tsx}'], 4 | darkMode: false, 5 | theme: { 6 | fontSize: { 7 | xs: '0.75rem', 8 | sm: '0.875rem', 9 | base: '1rem', 10 | lg: '1.125rem', 11 | xl: '1.25rem', 12 | '2xl': '1.5rem', 13 | '3xl': '1.875rem', 14 | '4xl': '2.25rem', 15 | '5xl': '3rem', 16 | '6xl': '4rem', 17 | }, 18 | extend: { 19 | colors: { 20 | gray: { 21 | 100: '#f7fafc', 22 | 200: '#edf2f7', 23 | 300: '#e2e8f0', 24 | 400: '#cbd5e0', 25 | 500: '#a0aec0', 26 | 600: '#718096', 27 | 700: '#4a5568', 28 | 800: '#2d3748', 29 | 900: '#1a202c', 30 | }, 31 | blue: { 32 | 100: '#ebf8ff', 33 | 200: '#bee3f8', 34 | 300: '#90cdf4', 35 | 400: '#63b3ed', 36 | 500: '#4299e1', 37 | 600: '#3182ce', 38 | 700: '#2b6cb0', 39 | 800: '#2c5282', 40 | 900: '#2a4365', 41 | }, 42 | }, 43 | }, 44 | }, 45 | variants: {}, 46 | plugins: [], 47 | }; 48 | -------------------------------------------------------------------------------- /packages/fa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/fa", 3 | "version": "0.0.2", 4 | "description": "A package to work with finite automata", 5 | "main": "dist/libs/index.js", 6 | "typings": "dist/libs/index.d.ts", 7 | "files": [ 8 | "dist/libs" 9 | ], 10 | "scripts": { 11 | "prebuild": "del-cli ./dist", 12 | "prepublishOnly": "npm run lint && npm run build && npm run test", 13 | "build": "tsc --sourceMap false", 14 | "build:watch": "tsc -w", 15 | "lint": "npx eslint \"./libs\" --ext tsx,ts", 16 | "lint:fix": "npx eslint \"./libs\" --ext tsx,ts --fix", 17 | "format": "npx prettier ./libs/**/*.{ts,js} --write", 18 | "test": "npx jest --runInBand" 19 | }, 20 | "keywords": [ 21 | "finite-automaton", 22 | "deterministic-finite-automaton", 23 | "non-deterministic-finite-automaton", 24 | "epsilon-finite-automaton" 25 | ], 26 | "author": "Safwan Shaheer ", 27 | "license": "MIT", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Devorein/fauton.git" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "dependencies": { 36 | "shortid": "^2.2.16" 37 | }, 38 | "devDependencies": {} 39 | } -------------------------------------------------------------------------------- /packages/fa/tests/DeterministicFiniteAutomaton/checkEquivalenceBetweenStatesGroups.test.ts: -------------------------------------------------------------------------------- 1 | import { checkEquivalenceBetweenStatesGroups } from '../../libs/DeterministicFiniteAutomaton/checkEquivalenceBetweenStatesGroups'; 2 | 3 | describe('checkEquivalenceBetweenStatesGroups', () => { 4 | it(`Detect equivalency between state groups`, () => { 5 | const stateGroupEquivalency = checkEquivalenceBetweenStatesGroups([ 6 | [['3', '5'], ['4'], ['1', '2']], 7 | [['1', '2'], ['4'], ['3', '5']], 8 | ]); 9 | expect(stateGroupEquivalency).toStrictEqual(true); 10 | }); 11 | 12 | it(`Detect non-equivalency with same length`, () => { 13 | // For different length of state groups 14 | const stateGroupEquivalency = checkEquivalenceBetweenStatesGroups([ 15 | [['3', '5'], ['4'], ['1', '2']], 16 | [['1', '2'], ['3', '5'], ['1']], 17 | ]); 18 | expect(stateGroupEquivalency).toStrictEqual(false); 19 | }); 20 | 21 | it(`Detect non-equivalency with different length`, () => { 22 | const stateGroupEquivalency = checkEquivalenceBetweenStatesGroups([ 23 | [['3', '5'], ['4'], ['1', '2']], 24 | [['4'], ['3', '5']], 25 | ]); 26 | expect(stateGroupEquivalency).toStrictEqual(false); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/fa/tests/utils/expandCharacterRanges.test.ts: -------------------------------------------------------------------------------- 1 | import { expandCharacterRanges } from '../../libs/utils/expandCharacterRanges'; 2 | 3 | it(`Should work with upper case`, () => { 4 | expect(expandCharacterRanges('A-E')).toStrictEqual(['A', 'B', 'C', 'D', 'E']); 5 | }); 6 | 7 | it(`Should work with negative numbers case`, () => { 8 | expect(expandCharacterRanges('-E')).toStrictEqual(['-E']); 9 | }); 10 | 11 | it(`Should work with lowercase case`, () => { 12 | expect(expandCharacterRanges('a-e')).toStrictEqual(['a', 'b', 'c', 'd', 'e']); 13 | }); 14 | 15 | it(`Should work with numbers`, () => { 16 | expect(expandCharacterRanges('1-5')).toStrictEqual(['1', '2', '3', '4', '5']); 17 | }); 18 | 19 | it(`Should work with multiple character classes`, () => { 20 | expect(expandCharacterRanges('a-e,1-5')).toStrictEqual([ 21 | 'a', 22 | 'b', 23 | 'c', 24 | 'd', 25 | 'e', 26 | '1', 27 | '2', 28 | '3', 29 | '4', 30 | '5', 31 | ]); 32 | }); 33 | 34 | it(`Should work for regular symbols classes`, () => { 35 | expect(expandCharacterRanges('a')).toStrictEqual(['a']); 36 | }); 37 | 38 | it(`Should skip expansion altogether`, () => { 39 | expect(expandCharacterRanges('a-b', true)).toStrictEqual(['a-b']); 40 | }); 41 | -------------------------------------------------------------------------------- /docs/src/theme/IconLightMode/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export default function IconLightMode(props) { 3 | return ( 4 | 5 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/fa/tests/DeterministicFiniteAutomaton/generateEquivalenceStates.test.ts: -------------------------------------------------------------------------------- 1 | import { generateEquivalenceStates } from '../../libs/DeterministicFiniteAutomaton/generateEquivalenceStates'; 2 | 3 | describe('generateEquivalenceStates', () => { 4 | it(`Generate equivalent state groups`, () => { 5 | const equivalentStateGroups = generateEquivalenceStates( 6 | { 7 | alphabets: ['0', '1'], 8 | states: ['0', '1', '2', '3', '4', '5', '6', '7'], 9 | transitions: { 10 | 0: { 11 | 0: ['1'], 12 | 1: ['5'], 13 | }, 14 | 1: { 15 | 0: ['6'], 16 | 1: ['2'], 17 | }, 18 | 2: { 19 | 0: ['0'], 20 | 1: ['2'], 21 | }, 22 | 3: { 23 | 0: ['2'], 24 | 1: ['6'], 25 | }, 26 | 4: { 27 | 0: ['7'], 28 | 1: ['5'], 29 | }, 30 | 5: { 31 | 0: ['2'], 32 | 1: ['6'], 33 | }, 34 | 6: { 35 | 0: ['6'], 36 | 1: ['4'], 37 | }, 38 | 7: { 39 | 0: ['6'], 40 | 1: ['2'], 41 | }, 42 | }, 43 | }, 44 | [['0', '1', '3', '4', '5', '6', '7'], ['2']] 45 | ); 46 | 47 | expect(equivalentStateGroups).toStrictEqual([['3', '5'], ['0', '4', '6'], ['1', '7'], ['2']]); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [14.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install dependencies 21 | run: | 22 | # Install vercel globally 23 | npm install vercel -g 24 | npm install 25 | npm run bootstrap 26 | - name: Deploy docs 27 | run: | 28 | cd ./docs 29 | npm install 30 | npm run build 31 | # Create the .vercel dir 32 | cd build 33 | mkdir .vercel 34 | ls -la . 35 | # Create vercel project.json 36 | echo {\"projectId\": \"$VERCEL_PROJECT_ID\", \"orgId\": \"$VERCEL_ORG_ID\"} > .vercel/project.json 37 | vercel --prod --token $VERCEL_TOKEN 38 | env: 39 | VERCEL_PROJECT_ID: ${{secrets.VERCEL_PROJECT_ID}} 40 | VERCEL_ORG_ID: ${{secrets.VERCEL_ORG_ID}} 41 | VERCEL_TOKEN: ${{secrets.VERCEL_TOKEN}} 42 | -------------------------------------------------------------------------------- /apps/landing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "removeComments": true, 12 | "preserveConstEnums": true, 13 | "strict": true, 14 | "alwaysStrict": true, 15 | "strictNullChecks": true, 16 | "noUncheckedIndexedAccess": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "allowUnreachableCode": false, 23 | "noFallthroughCasesInSwitch": true, 24 | "target": "ES2018", 25 | "outDir": "out", 26 | "declaration": true, 27 | "sourceMap": true, 28 | "esModuleInterop": true, 29 | "allowSyntheticDefaultImports": true, 30 | "allowJs": false, 31 | "skipLibCheck": true, 32 | "forceConsistentCasingInFileNames": true, 33 | "jsx": "preserve", 34 | "noEmit": true, 35 | "isolatedModules": true, 36 | "incremental": true 37 | }, 38 | "exclude": [ 39 | "./out/**/*", 40 | "./node_modules/**/*" 41 | ], 42 | "include": [ 43 | "next-env.d.ts", 44 | "**/*.ts", 45 | "**/*.tsx" 46 | ] 47 | } -------------------------------------------------------------------------------- /apps/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "removeComments": true, 12 | "preserveConstEnums": true, 13 | "strict": true, 14 | "alwaysStrict": true, 15 | "strictNullChecks": true, 16 | "noUncheckedIndexedAccess": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "allowUnreachableCode": false, 23 | "noFallthroughCasesInSwitch": true, 24 | "target": "ES2018", 25 | "outDir": "out", 26 | "declaration": true, 27 | "sourceMap": true, 28 | "esModuleInterop": true, 29 | "allowSyntheticDefaultImports": true, 30 | "allowJs": false, 31 | "skipLibCheck": true, 32 | "forceConsistentCasingInFileNames": true, 33 | "jsx": "preserve", 34 | "noEmit": true, 35 | "isolatedModules": true, 36 | "incremental": true 37 | }, 38 | "exclude": [ 39 | "./out/**/*", 40 | "./node_modules/**/*" 41 | ], 42 | "include": [ 43 | "next-env.d.ts", 44 | "**/*.ts", 45 | "**/*.tsx" 46 | ] 47 | } -------------------------------------------------------------------------------- /packages/testing/libs/utils/generateCaseMessage.ts: -------------------------------------------------------------------------------- 1 | import colors from 'colors'; 2 | 3 | /** 4 | * Generate case message for each input string 5 | * @param isWrong Whether or not the case is wrong 6 | * @param inputString The input string that was used in the case 7 | * @param automataTestResult Verdict of the automata test for the input string 8 | * @param logicTestResult Verdict of the login test for the input string 9 | * @returns 10 | */ 11 | export function generateCaseMessage( 12 | isWrong: boolean, 13 | inputString: string, 14 | automataTestResult: boolean, 15 | logicTestResult: boolean 16 | ) { 17 | // Generate two strings both with and without colors 18 | // Coloured could be used inside a terminal 19 | // While non coloured could be used for storing to a file 20 | return { 21 | withColors: `${[ 22 | `V: ${isWrong ? colors.red.bold(`WRONG`) : colors.green.bold(`CORRECT`)}`, 23 | `I: ${colors.yellow.bold(inputString)}`, 24 | `L: ${colors.blue.bold(logicTestResult.toString())}`, 25 | `A: ${colors.blue.bold(automataTestResult.toString())}`, 26 | ].join('\n')}\n`, 27 | withoutColors: `${[ 28 | `V: ${isWrong ? `WRONG` : `CORRECT`}`, 29 | `I: ${inputString}`, 30 | `L: ${logicTestResult}`, 31 | `A: ${automataTestResult}`, 32 | ].join('\n')}\n`, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /apps/playground/src/components/Drawer.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import MuiDrawer from '@mui/material/Drawer'; 3 | import * as React from 'react'; 4 | import { DrawerContext } from '../contexts/DrawerContext'; 5 | 6 | interface DrawerProps { 7 | drawerContent: React.ReactNode 8 | } 9 | 10 | export default function Drawer(props: DrawerProps) { 11 | const {drawerContent} = props; 12 | const {isDrawerOpen, setIsDrawerOpen} = React.useContext(DrawerContext); 13 | 14 | const toggleDrawer = (open: boolean, event: React.KeyboardEvent) => { 15 | if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) { 16 | return; 17 | } 18 | 19 | setIsDrawerOpen(open); 20 | }; 21 | 22 | return ( 23 | toggleDrawer(false, event as any)} 27 | sx={{ 28 | p: 1 29 | }} 30 | > 31 | toggleDrawer(false ,event as unknown as React.KeyboardEvent)} 36 | onKeyDown={(event) => toggleDrawer(false ,event)} 37 | > 38 | {drawerContent} 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeNonDeterminism.test.ts: -------------------------------------------------------------------------------- 1 | import { removeNonDeterminism } from '../libs/removeNonDeterminism'; 2 | 3 | describe('removeNonDeterminism', () => { 4 | it(`Should be able to left factor for multiple production rule`, () => { 5 | const leftRefactored = removeNonDeterminism({ 6 | productionRules: { 7 | S: ["a A d", "a B"], 8 | A: ["a", "a b"], 9 | B: ["c c d", "d d c"] 10 | }, 11 | startVariable: "S" 12 | }); 13 | expect(leftRefactored).toStrictEqual({ 14 | productionRules: { 15 | S: ["a S'"], 16 | "S'": ['A d', 'B'], 17 | A: ["a A'"], 18 | "A'": ['', 'b'], 19 | B: ['c c d', 'd d c'], 20 | }, 21 | startVariable: 'S', 22 | variables: ['S', "S'", 'A', "A'", 'B'], 23 | terminals: ['a', 'd', 'b', 'c'], 24 | }); 25 | }); 26 | 27 | it(`Should be able to left factor for single production rule`, () => { 28 | const leftRefactored = removeNonDeterminism({ 29 | productionRules: { 30 | S: ['b', 'a S S b S', 'a S a S b', 'a b b'], 31 | }, 32 | startVariable: 'S', 33 | }); 34 | expect(leftRefactored).toStrictEqual({ 35 | productionRules: { 36 | S: ['b', "a S'"], 37 | "S'": ["S S''", 'b b'], 38 | "S''": ['S b S', 'a S b'], 39 | }, 40 | startVariable: 'S', 41 | variables: ['S', "S'", "S''"], 42 | terminals: ['b', 'a'], 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/cfg/tests/generateLL1ParsingTable.test.ts: -------------------------------------------------------------------------------- 1 | import { generateLL1ParsingTable } from '../libs/generateLL1ParsingTable'; 2 | 3 | describe('generateLL1ParsingTable', () => { 4 | it(`Simple LL1 non parsable grammar`, () => { 5 | const ll1ParsingTable = generateLL1ParsingTable({ 6 | productionRules: { 7 | S: ['a S b S', 'b S a S', ''], 8 | }, 9 | }); 10 | expect( 11 | ll1ParsingTable 12 | ).toStrictEqual({ parseTable: { S: { a: 2, b: 2, $: 2 } }, isParsable: false }); 13 | }); 14 | 15 | it(`Complex LL1 parsable grammar`, () => { 16 | const ll1ParsingTable = generateLL1ParsingTable({ 17 | productionRules: { 18 | E: ["T E'"], 19 | "E'": ["+ T E'", ''], 20 | T: ["F T'"], 21 | "T'": ["* F T'", ''], 22 | F: ['id', '( E )'], 23 | }, 24 | }) 25 | expect( 26 | ll1ParsingTable 27 | ).toStrictEqual({ 28 | parseTable: { 29 | E: { 30 | id: 0, 31 | '(': 0, 32 | }, 33 | "E'": { 34 | '+': 0, 35 | ')': 1, 36 | $: 1, 37 | }, 38 | T: { 39 | id: 0, 40 | '(': 0, 41 | }, 42 | "T'": { 43 | '+': 1, 44 | '*': 0, 45 | ')': 1, 46 | $: 1, 47 | }, 48 | F: { 49 | id: 0, 50 | '(': 1, 51 | }, 52 | }, 53 | isParsable: true, 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeUnitProduction.test.ts: -------------------------------------------------------------------------------- 1 | import { findFirstUnitProductionRule, removeUnitProduction } from '../libs/removeUnitProduction'; 2 | 3 | describe('.findFirstUnitProductionRule', () => { 4 | it(`Should find first unit production rule`, () => { 5 | expect( 6 | findFirstUnitProductionRule(['Sub', 'Adj', 'Verb', 'Conj'], { 7 | Sub: ['0 Adj', '1 Verb', 'Conj'], 8 | Adj: ['0 Sub', '0 0'], 9 | Verb: ['1', 'Adj'], 10 | Conj: ['0 1'], 11 | }) 12 | ).toStrictEqual(['Sub', 2]); 13 | }); 14 | 15 | it(`Should return null if no production rule returns unit`, () => { 16 | expect( 17 | findFirstUnitProductionRule(['Sub', 'Adj', 'Verb'], { 18 | Sub: ['0 Adj', '1 Verb'], 19 | Adj: ['0 Sub', '0 0'], 20 | Verb: ['1'], 21 | }) 22 | ).toStrictEqual(null); 23 | }); 24 | }); 25 | 26 | describe('removeUnitProduction', () => { 27 | it(`Remove unit production`, () => { 28 | const productionRules = { 29 | Sub: ['0 Adj', '1 Verb', 'Conj'], 30 | Adj: ['0 Sub', '0 0'], 31 | Verb: ['1', 'Adj'], 32 | Conj: ['0 1'], 33 | }; 34 | 35 | removeUnitProduction({ productionRules, variables: ['Sub', 'Adj', 'Verb', 'Conj'] }); 36 | 37 | expect(productionRules).toStrictEqual({ 38 | Sub: ['0 Adj', '1 Verb', '0 1'], 39 | Adj: ['0 Sub', '0 0'], 40 | Verb: ['1', '0 Sub', '0 0'], 41 | Conj: ['0 1'], 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /docs/static/img/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fauton", 3 | "homepage": "https://github.com/Devorein/fauton/blob/master/README.md", 4 | "bugs": { 5 | "url": "https://github.com/Devorein/fauton/issues" 6 | }, 7 | "scripts": { 8 | "prebuild": "lerna run prebuild", 9 | "build": "lerna run build", 10 | "test": "lerna run test", 11 | "lint": "lerna run lint", 12 | "format": "lerna run format", 13 | "build:watch": "lerna run build:watch --parallel", 14 | "bootstrap": "lerna bootstrap" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Devorein/fauton.git" 19 | }, 20 | "author": "Safwan Shaheer ", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@types/cli-progress": "^3.9.2", 24 | "@types/jest": "^27.0.2", 25 | "@types/node": "^16.11.6", 26 | "@types/shortid": "^0.0.29", 27 | "@typescript-eslint/eslint-plugin": "^5.4.0", 28 | "@typescript-eslint/parser": "^5.4.0", 29 | "del-cli": "^4.0.1", 30 | "eslint": "^8.2.0", 31 | "eslint-config-airbnb-base": "^15.0.0", 32 | "eslint-config-prettier": "^8.3.0", 33 | "eslint-import-resolver-node": "^0.3.6", 34 | "eslint-plugin-import": "^2.25.3", 35 | "eslint-plugin-prettier": "^4.0.0", 36 | "jest": "^27.3.1", 37 | "np": "^7.5.0", 38 | "prettier": "^2.4.1", 39 | "ts-jest": "^27.0.7", 40 | "typescript": "^4.4.4" 41 | }, 42 | "dependencies": { 43 | "lerna": "^4.0.0" 44 | } 45 | } -------------------------------------------------------------------------------- /packages/cfg/libs/convertStringToGrammar.ts: -------------------------------------------------------------------------------- 1 | import { extractTerminalsFromCfg } from './extractTerminalsFromCfg'; 2 | import { IContextFreeGrammar } from './types'; 3 | 4 | /** 5 | * Convert a string representation of cfg object to a cfg object 6 | * @param grammarString Grammar string to convert to cfg object 7 | * @returns Converted cfg object 8 | */ 9 | export function convertStringToGrammar(grammarString: string): IContextFreeGrammar { 10 | const productionRules = grammarString.split('\n'); 11 | 12 | const cfg: IContextFreeGrammar = { 13 | productionRules: {}, 14 | startVariable: '', 15 | terminals: [], 16 | variables: [], 17 | }; 18 | 19 | productionRules.forEach((productionRule) => { 20 | const [variable, rules] = productionRule.split(' -> '); 21 | // Only create the production rule if it doesn't exist, otherwise we might replace the existing one 22 | if (!cfg.productionRules[variable]) { 23 | cfg.productionRules[variable] = []; 24 | } 25 | 26 | // Each production rule is separated by | 27 | rules.split(' | ').forEach((rule) => { 28 | cfg.productionRules[variable].push(rule); 29 | }); 30 | cfg.variables.push(variable); 31 | }); 32 | 33 | cfg.variables = Array.from(new Set(cfg.variables)); 34 | // Set the first variable as the start variable 35 | // eslint-disable-next-line 36 | cfg.startVariable = cfg.variables[0]; 37 | 38 | cfg.terminals = extractTerminalsFromCfg(cfg); 39 | return cfg; 40 | } 41 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle @docusaurus/theme-classic", 10 | "deploy": "vercel ./build --prod", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^2.0.0-beta.20", 19 | "@docusaurus/preset-classic": "^2.0.0-beta.20", 20 | "@mdx-js/react": "^1.6.21", 21 | "clsx": "^1.1.1", 22 | "docusaurus-plugin-exgen": "0.0.1", 23 | "prism-react-renderer": "^1.3.3", 24 | "react": "^17.0.1", 25 | "react-dom": "^17.0.1", 26 | "react-toggle": "^4.1.2" 27 | }, 28 | "devDependencies": { 29 | "@docusaurus/module-type-aliases": "^2.0.0-beta.20", 30 | "@tsconfig/docusaurus": "^1.0.5", 31 | "docusaurus-plugin-typedoc": "^0.17.5", 32 | "typedoc": "^0.22.15", 33 | "typedoc-plugin-markdown": "^3.12.1", 34 | "typescript": "^4.6.4" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.5%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeNonTerminableProduction.test.ts: -------------------------------------------------------------------------------- 1 | import { removeNonTerminableProduction } from '../libs/removeNonTerminableProduction'; 2 | 3 | describe('removeNonTerminableProduction', () => { 4 | it(`Remove non terminable production`, () => { 5 | const cfg = { 6 | productionRules: { 7 | S: ['Adj Con', 'Verb'], 8 | Adj: ['another'], 9 | Con: ['can', 'Verb Con'], 10 | E: ['another Adj', 'early'], 11 | Verb: ['Verb'], 12 | }, 13 | startVariable: 'S', 14 | // Note that production rules for Noun variable is not present, 15 | variables: ['S', 'Adj', 'Con', 'E', 'Verb', 'Noun'], 16 | terminals: ['another', 'can', 'early'], 17 | }; 18 | 19 | const terminalVariables = removeNonTerminableProduction(cfg); 20 | expect(terminalVariables).toStrictEqual(['S', 'Adj', 'Con', 'E']); 21 | expect(cfg.productionRules).toStrictEqual({ 22 | S: ['Adj Con'], 23 | Adj: ['another'], 24 | Con: ['can'], 25 | E: ['another Adj', 'early'], 26 | }); 27 | }); 28 | }); 29 | 30 | it(`Should throw error if start variable is non terminable`, () => { 31 | const cfg = { 32 | productionRules: { 33 | S: ['S', 'Verb'], 34 | Verb: ['Verb'], 35 | }, 36 | startVariable: 'S', 37 | // Note that production rules for Noun variable is not present, 38 | variables: ['S', 'Verb'], 39 | terminals: ['another', 'can', 'early'], 40 | }; 41 | 42 | expect(() => removeNonTerminableProduction(cfg)).toThrow(`This grammar can't be convert to cnf`); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeLeftRecursion.test.ts: -------------------------------------------------------------------------------- 1 | import { removeLeftRecursion } from '../libs/removeLeftRecursion'; 2 | 3 | describe('removeLeftRecursion', () => { 4 | it(`Should remove both direct and indirect left recursion`, () => { 5 | const leftRecursionRemovedCfg = removeLeftRecursion({ 6 | productionRules: { 7 | S: ["A f", "b"], 8 | A: ["A c", "S d", "B e", "C"], 9 | B: ["A g", "S h", "k"], 10 | C: ["B k m A", "A S", "j"], 11 | }, 12 | startVariable: "S" 13 | }); 14 | expect(leftRecursionRemovedCfg).toStrictEqual({ 15 | productionRules: { 16 | S: ['A f', 'b'], 17 | A: ["b d A'", "B e A'", "C A'"], 18 | "A'": ["c A'", "f d A'", ''], 19 | B: ["b d A' g B'", "C A' g B'", "b d A' f h B'", "C A' f h B'", "b h B'", "k B'"], 20 | "B'": ["e A' g B'", "e A' f h B'", ''], 21 | C: [ 22 | "b d A' g B' k m A C'", 23 | "b d A' f h B' k m A C'", 24 | "b h B' k m A C'", 25 | "k B' k m A C'", 26 | "b d A' S C'", 27 | "b d A' g B' e A' S C'", 28 | "b d A' f h B' e A' S C'", 29 | "b h B' e A' S C'", 30 | "k B' e A' S C'", 31 | "j C'", 32 | ], 33 | "C'": [ 34 | "A' g B' k m A C'", 35 | "A' f h B' k m A C'", 36 | "A' g B' e A' S C'", 37 | "A' f h B' e A' S C'", 38 | "A' S C'", 39 | '', 40 | ], 41 | }, 42 | startVariable: 'S', 43 | variables: ['S', 'A', "A'", 'B', "B'", 'C', "C'"], 44 | terminals: ['f', 'b', 'd', 'e', 'c', 'g', 'h', 'k', 'm', 'j'], 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/regex/libs/utils/addConcatOperatorToRegex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add concat (.) operator to regex string where necessary 3 | * @param regexString Regex string to add concat operator to 4 | * @returns Concat operator added regex string 5 | */ 6 | export function addConcatOperatorToRegex(regexString: string) { 7 | if (regexString.length === 1) { 8 | return regexString; 9 | } 10 | 11 | const regexOperatorGroup1 = new Set('(*+?|'); 12 | const regexOperatorGroup2 = new Set(')*+?|'); 13 | const regexOperatorGroup3 = new Set('*+?'); 14 | 15 | let newRegexString = ''; 16 | for (let index = 0; index < regexString.length; index += 1) { 17 | const regexSymbol = regexString[index]; 18 | newRegexString += regexSymbol; 19 | // Break on last character 20 | // We could've looped one less time 21 | // but then the last symbol wouldn't have been added 22 | if (index === regexString.length -1 ) { 23 | break; 24 | } 25 | // add concatenation operator between 26 | // literal.literal, right_bracket.left_bracket, literal.left_bracket 27 | if ( 28 | !regexOperatorGroup1.has(regexSymbol) && 29 | !regexOperatorGroup2.has(regexString[index + 1]) 30 | ) { 31 | newRegexString += '.'; 32 | } else if ( 33 | // add concatenation operator between 34 | // +*? and ( or literal 35 | regexOperatorGroup3.has(regexSymbol) && 36 | !regexOperatorGroup2.has(regexString[index + 1]) 37 | ) { 38 | newRegexString += '.'; 39 | } 40 | } 41 | return newRegexString; 42 | } 43 | -------------------------------------------------------------------------------- /packages/fa/libs/utils/expandCharacterRanges.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates an array of symbols by expanding the character classes string separate by , 3 | * @param characterRangesString String containing character classes separated by , 4 | * @returns An array of symbols that are present in the character classes 5 | */ 6 | export function expandCharacterRanges(characterRangesString: string, skipExpansion?: boolean) { 7 | const shouldSkipExpansion = skipExpansion ?? false; 8 | 9 | if (!shouldSkipExpansion) { 10 | const characterRanges = characterRangesString.split(','); 11 | const symbols: Set = new Set(); 12 | characterRanges.forEach((characterRange) => { 13 | // Handling negative signs 14 | if (characterRange.length === 2) { 15 | symbols.add(characterRange); 16 | } else { 17 | const characterRangeRanges = characterRange.split('-'); 18 | if (characterRangeRanges.length === 2) { 19 | const startCharacter = characterRangeRanges[0].charCodeAt(0); 20 | const endCharacter = characterRangeRanges[1].charCodeAt(0); 21 | for ( 22 | let characterCodePoint = startCharacter; 23 | characterCodePoint <= endCharacter; 24 | characterCodePoint += 1 25 | ) { 26 | symbols.add(String.fromCodePoint(characterCodePoint)); 27 | } 28 | } else { 29 | // When its a single symbol 30 | symbols.add(characterRange); 31 | } 32 | } 33 | }); 34 | return Array.from(symbols); 35 | } else { 36 | return [characterRangesString]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cfg/libs/utils/generateNewVariable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a random integer between two intervals 3 | * @param min Left limit of generated int 4 | * @param max Right limit of generated int 5 | * @returns A random integer 6 | */ 7 | function randomIntFromInterval(min: number, max: number) { 8 | return Math.floor(Math.random() * (max - min + 1) + min); 9 | } 10 | 11 | const CAPITAL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 12 | const DIGITS = '0123456789'; 13 | 14 | /** 15 | * Generates a new variable that is not part of the passed variables array 16 | * @param variables A set of variables which shouldn't be generated 17 | * @returns Generated variable string 18 | */ 19 | export function generateNewVariable(variables: string[]) { 20 | const variablesSet = new Set(variables); 21 | // Create a variable with first character to be any capital letter and 2nd letter to be digit 22 | let newVariable = 23 | CAPITAL_LETTERS[randomIntFromInterval(0, CAPITAL_LETTERS.length - 1)] + 24 | DIGITS[randomIntFromInterval(0, DIGITS.length - 1)]; 25 | 26 | // While the new variable is present in our variables set we need to keep on creating it 27 | while (variablesSet.has(newVariable)) { 28 | // New variable will be a combination of capital letter and a digit 29 | newVariable = 30 | CAPITAL_LETTERS[randomIntFromInterval(0, CAPITAL_LETTERS.length - 1)] + 31 | DIGITS[randomIntFromInterval(0, DIGITS.length - 1)]; 32 | } 33 | // Push the newly generated variable to the passed variables array 34 | variables.push(newVariable); 35 | return newVariable; 36 | } -------------------------------------------------------------------------------- /packages/cfg/libs/utils/removeProductionRules.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar } from '../types'; 2 | import { setDifference } from './setOperations'; 3 | 4 | export function removeProductionRules( 5 | cfg: Pick & { 6 | removedVariables: string[]; 7 | } 8 | ) { 9 | const { productionRules, removedVariables, variables } = cfg; 10 | // Remove production variables from the production rules record 11 | removedVariables.forEach((removedVariable) => { 12 | delete productionRules[removedVariable]; 13 | }); 14 | const removedVariablesSet = new Set(removedVariables); 15 | 16 | // Now we need to remove all the production rules that references any removed production variables 17 | Object.entries(productionRules).forEach(([productionVariable, productionRulesSubstitutions]) => { 18 | productionRules[productionVariable] = productionRulesSubstitutions.filter( 19 | (productionRulesSubstitution) => { 20 | // productionRulesSubstitution = Verb Adj Noun 21 | const productionRulesSubstitutionTokensSet = new Set( 22 | productionRulesSubstitution.split(' ') 23 | ); 24 | const difference = setDifference(productionRulesSubstitutionTokensSet, removedVariablesSet); 25 | // If we dont need to remove any variables then the size before and after the set difference would be similar 26 | return difference.size === productionRulesSubstitutionTokensSet.size; 27 | } 28 | ); 29 | }); 30 | 31 | return Array.from(setDifference(new Set(variables), new Set(removedVariables))); 32 | } 33 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | packages=( cfg fa testing ) 4 | 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | BLUE='\033[0;34m' 8 | NC='\033[0m' 9 | 10 | echo $GITHUB_WORKSPACE 11 | 12 | for package in "${packages[@]}" ; do 13 | package_name="@fauton/$package" 14 | cd "${GITHUB_WORKSPACE}/packages/$package" 15 | tsc="${GITHUB_WORKSPACE}/node_modules/.bin/tsc" 16 | jest="${GITHUB_WORKSPACE}/node_modules/.bin/jest" 17 | eslint="${GITHUB_WORKSPACE}/node_modules/.bin/eslint" 18 | 19 | # if ! (npm install -g) then 20 | # echo -e "${RED}Error installing $package_name globally${NC}" 21 | # exit 1 22 | # else 23 | # echo -e "${GREEN}Successfully installed $package_name globally${NC}" 24 | # fi 25 | 26 | if ! (npm install) then 27 | echo -e "${RED}Error installing $package_name dependencies${NC}" 28 | exit 1 29 | else 30 | echo -e "${GREEN}Successfully installed $package_name dependencies${NC}" 31 | fi 32 | 33 | if ! (node $eslint ./libs --ext tsx,ts) then 34 | echo -e "${RED}Error linting $package_name${NC}" 35 | exit 1 36 | else 37 | echo -e "${GREEN}Successfully linted $package_name${NC}" 38 | fi 39 | 40 | if ! (node $tsc --sourceMap false) then 41 | echo -e "${RED}Error building $package_name${NC}" 42 | exit 1 43 | else 44 | echo -e "${GREEN}Successfully build $package_name${NC}" 45 | fi 46 | 47 | if ! (node $jest --runInBand) then 48 | echo -e "${RED}Error testing $package_name${NC}" 49 | exit 1 50 | else 51 | echo -e "${GREEN}Successfully tested $package_name${NC}" 52 | fi 53 | done -------------------------------------------------------------------------------- /packages/regex/tests/addConcatOperatorToRegex.test.ts: -------------------------------------------------------------------------------- 1 | import { addConcatOperatorToRegex } from "../libs/utils/addConcatOperatorToRegex"; 2 | 3 | describe('addConcatOperatorToRegex', () => { 4 | it(`Single length regex string`, () => { 5 | const concatOperatorAddedRegexString = addConcatOperatorToRegex("a"); 6 | expect(concatOperatorAddedRegexString).toBe("a") 7 | }) 8 | 9 | it(`Multi length regex string (between two literals)`, () => { 10 | const concatOperatorAddedRegexString = addConcatOperatorToRegex("ab"); 11 | expect(concatOperatorAddedRegexString).toStrictEqual("a.b") 12 | }) 13 | 14 | it(`Multi length regex string (between two opposite brackets)`, () => { 15 | const concatOperatorAddedRegexString = addConcatOperatorToRegex(")("); 16 | expect(concatOperatorAddedRegexString).toStrictEqual(").(") 17 | }) 18 | 19 | it(`Multi length regex string (between literal and left bracket)`, () => { 20 | const concatOperatorAddedRegexString = addConcatOperatorToRegex("a("); 21 | expect(concatOperatorAddedRegexString).toStrictEqual("a.(") 22 | }) 23 | 24 | it(`Multi length regex string (between operator and left bracket)`, () => { 25 | const concatOperatorAddedRegexString = addConcatOperatorToRegex("+("); 26 | expect(concatOperatorAddedRegexString).toStrictEqual("+.(") 27 | }) 28 | 29 | it(`Multi length regex string (between operator and literal)`, () => { 30 | const concatOperatorAddedRegexString = addConcatOperatorToRegex("+a"); 31 | expect(concatOperatorAddedRegexString).toStrictEqual("+.a") 32 | }) 33 | }) -------------------------------------------------------------------------------- /packages/testing/libs/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | export interface AutomatonTestInfo { 4 | falsePositives: number; 5 | falseNegatives: number; 6 | truePositives: number; 7 | trueNegatives: number; 8 | } 9 | 10 | export interface IOutputFiles { 11 | case: boolean; 12 | incorrect: boolean; 13 | correct: boolean; 14 | input: boolean; 15 | aggregate: boolean; 16 | accepted: boolean; 17 | rejected: boolean; 18 | } 19 | 20 | export type InputStringOption = 21 | | { 22 | type: 'generate'; 23 | random?: { 24 | total: number; 25 | minTokenLength: number; 26 | maxTokenLength: number; 27 | }; 28 | combo?: undefined | null; 29 | outputFiles?: Partial; 30 | } 31 | | { 32 | type: 'generate'; 33 | combo: { 34 | maxTokenLength: number; 35 | startLength?: number; 36 | }; 37 | random?: undefined | null; 38 | outputFiles?: Partial; 39 | } 40 | | { 41 | type: 'file'; 42 | filePath: string; 43 | outputFiles?: Partial; 44 | } 45 | | { 46 | type: 'custom'; 47 | inputs: string[][]; 48 | outputFiles?: Partial; 49 | }; 50 | 51 | export type IAutomatonTestLogicFn = ( 52 | inputTokens: string[], 53 | automatonTestResult: boolean 54 | ) => boolean; 55 | export type IAutomatonTestFn = (inputTokens: string[]) => boolean; 56 | 57 | export interface IAutomatonInfo { 58 | test: IAutomatonTestFn; 59 | testLogic: IAutomatonTestLogicFn; 60 | automaton: { 61 | label: string; 62 | alphabets: string[]; 63 | description?: string; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /packages/cfg/tests/generateLR0ParsingTable.test.ts: -------------------------------------------------------------------------------- 1 | import { addDotToProductionRule, augmentCfg, generateClosureOfLR0Item } from "../libs/generateLR0ParsingTable"; 2 | 3 | describe('addDotToProductionRule', () => { 4 | it(`Simple grammar`, () => { 5 | const productionRules = { 6 | S: ["A A"], 7 | A: ["a A", "b"] 8 | }; 9 | 10 | addDotToProductionRule(productionRules, "S") 11 | 12 | expect(productionRules).toStrictEqual({ 13 | S: ["$.$ A A"], 14 | A: ["a A", "b"] 15 | }) 16 | }) 17 | }) 18 | 19 | describe('generateClosureOfLR0Item', () => { 20 | it(`Simple grammar`, () => { 21 | const productionRules = { 22 | "S'": ["$.$ S"], 23 | S: ["A A"], 24 | A: ["a A", "b"] 25 | }; 26 | 27 | generateClosureOfLR0Item({ 28 | productionRules, 29 | variables: ["S'", "S", "A"] 30 | }, "S'") 31 | 32 | expect(productionRules).toStrictEqual({ 33 | "S'": ["$.$ S"], 34 | S: ["$.$ A A"], 35 | A: ["$.$ a A", "$.$ b"] 36 | }) 37 | }) 38 | }) 39 | 40 | describe('augmentCfg', () => { 41 | it(`Simple grammar`, () => { 42 | const augmentedGrammar = augmentCfg({ 43 | productionRules: { 44 | S: ["A a", "B"], 45 | A: ["a b"], 46 | B: ["S a"] 47 | }, 48 | }) 49 | 50 | expect(augmentedGrammar).toStrictEqual({ 51 | productionRules: { 52 | "S'": ["S"], 53 | S: ["A a", "B"], 54 | A: ["a b"], 55 | B: ["S a"] 56 | }, 57 | variables: [ "S'", "S", "A", "B"], 58 | startVariable: "S'", 59 | terminals: ["a", "b"] 60 | }) 61 | }) 62 | }) -------------------------------------------------------------------------------- /packages/cfg/tests/generateVariableReferenceRecord.test.ts: -------------------------------------------------------------------------------- 1 | import generateVariableReferenceRecord from '../libs/generateVariableReferenceRecord'; 2 | 3 | describe('generateVariableReferenceRecord', () => { 4 | it(`Get variable referenced in all rules`, () => { 5 | expect( 6 | generateVariableReferenceRecord({ 7 | productionRules: { 8 | A: ['word1 A word2', 'B A word3'], 9 | B: ['A B', 'C B B word4'], 10 | C: ['word1', 'C A'], 11 | }, 12 | variables: ['A', 'B', 'C'], 13 | }) 14 | ).toStrictEqual({ 15 | A: [ 16 | { 17 | variable: 'A', 18 | tokenNumber: 1, 19 | ruleNumber: 0, 20 | }, 21 | { 22 | variable: 'A', 23 | tokenNumber: 1, 24 | ruleNumber: 1, 25 | }, 26 | { 27 | variable: 'B', 28 | tokenNumber: 0, 29 | ruleNumber: 0, 30 | }, 31 | { 32 | variable: 'C', 33 | tokenNumber: 1, 34 | ruleNumber: 1, 35 | }, 36 | ], 37 | B: [ 38 | { 39 | variable: 'A', 40 | tokenNumber: 0, 41 | ruleNumber: 1, 42 | }, 43 | { 44 | variable: 'B', 45 | tokenNumber: 1, 46 | ruleNumber: 0, 47 | }, 48 | { 49 | variable: 'B', 50 | tokenNumber: 1, 51 | ruleNumber: 1, 52 | }, 53 | { 54 | variable: 'B', 55 | tokenNumber: 2, 56 | ruleNumber: 1, 57 | }, 58 | ], 59 | C: [ 60 | { 61 | variable: 'B', 62 | tokenNumber: 0, 63 | ruleNumber: 1, 64 | }, 65 | { 66 | variable: 'C', 67 | tokenNumber: 0, 68 | ruleNumber: 1, 69 | } 70 | ] 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/fa/libs/NonDeterministicFiniteAutomaton/moveAndEpsilonClosureStateSet.ts: -------------------------------------------------------------------------------- 1 | import { TransformedFiniteAutomaton } from '../types'; 2 | import { epsilonClosureOfState } from './epsilonClosureOfState'; 3 | 4 | /** 5 | * Move_DFA(states_arr, symbol) 6 | * @param transitions Transition record of automaton 7 | * @param epsilonTransitions epsilon transitions record of automaton 8 | * @param states array of states 9 | * @param symbol Symbol for which to find e-closure of 10 | * @returns array of states obtained by epsilon closure for a symbol 11 | */ 12 | export function moveAndEpsilonClosureStateSet( 13 | transitions: TransformedFiniteAutomaton['transitions'], 14 | epsilonTransitions: TransformedFiniteAutomaton['epsilon_transitions'], 15 | states: string[], 16 | symbol: string 17 | ) { 18 | const transitionRecordExtractedStates: Set = new Set(); 19 | states.forEach((state) => { 20 | const statesForSymbol = transitions[state]?.[symbol]; 21 | if (statesForSymbol) { 22 | statesForSymbol.forEach((stateForSymbol) => 23 | transitionRecordExtractedStates.add(stateForSymbol) 24 | ); 25 | } 26 | }); 27 | const finalStates = new Set(transitionRecordExtractedStates); 28 | // No need to calculate epsilon closures of regular nfa 29 | if (epsilonTransitions) { 30 | transitionRecordExtractedStates.forEach((transitionRecordExtractedState) => { 31 | const epsilonClosuredStates = epsilonClosureOfState( 32 | epsilonTransitions, 33 | transitionRecordExtractedState 34 | ); 35 | epsilonClosuredStates.forEach((epsilonClosuredState) => { 36 | finalStates.add(epsilonClosuredState); 37 | }); 38 | }); 39 | } 40 | return Array.from(finalStates); 41 | } 42 | -------------------------------------------------------------------------------- /packages/cfg/libs/parseWithLL1Table.ts: -------------------------------------------------------------------------------- 1 | import { generateLL1ParsingTable } from "./generateLL1ParsingTable"; 2 | import { generateParseTreeFromDerivations } from "./generateParseTreeFromDerivations"; 3 | import { Derivation, IContextFreeGrammarInput } from "./types"; 4 | import { populateCfg } from "./utils/populateCfg"; 5 | 6 | export function parseWithLL1Table(inputCfg: IContextFreeGrammarInput, textContent: string) { 7 | const derivations: Derivation[] = [] 8 | const cfg = populateCfg(inputCfg) 9 | const {parseTable} = generateLL1ParsingTable(cfg); 10 | const ruleStack = ["$", cfg.startVariable]; 11 | let lookAheadPointer = 0; 12 | let parsed: null | boolean = null 13 | while (ruleStack.length !== 1 && lookAheadPointer !== textContent.length) { 14 | const variableAtStackTop = ruleStack.pop()!; 15 | if (variableAtStackTop === textContent[lookAheadPointer]) { 16 | lookAheadPointer += 1 17 | } else { 18 | const char = textContent[lookAheadPointer]; 19 | if (char in parseTable[variableAtStackTop]) { 20 | const ruleNumber = parseTable[variableAtStackTop][char]!; 21 | const productionRule = cfg.productionRules[variableAtStackTop][ruleNumber]; 22 | const productionRuleTokens = productionRule.split(" "); 23 | ruleStack.push(...[...productionRuleTokens].reverse()) 24 | derivations.push([variableAtStackTop, productionRuleTokens]) 25 | } else { 26 | parsed = false; 27 | break; 28 | } 29 | } 30 | } 31 | 32 | return { 33 | parsed: parsed ?? (ruleStack.length === 1 && ruleStack[0] === "$"), 34 | derivations, 35 | tree: generateParseTreeFromDerivations(cfg.variables, derivations) 36 | } 37 | } -------------------------------------------------------------------------------- /apps/landing/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Configuration for JavaScript files 3 | extends: ['airbnb-base', 'plugin:prettier/recommended'], 4 | rules: { 5 | 'prettier/prettier': 'off', 6 | }, 7 | overrides: [ 8 | // Configuration for TypeScript files 9 | { 10 | files: ['**/*.ts', '**/*.tsx'], 11 | plugins: ['@typescript-eslint', 'unused-imports'], 12 | extends: ['airbnb-typescript', 'next', 'next/core-web-vitals', 'plugin:prettier/recommended'], 13 | parserOptions: { 14 | project: './tsconfig.json', 15 | tsconfigRootDir: __dirname, 16 | }, 17 | rules: { 18 | 'one-var': 'off', 19 | 'prefer-destructuring': 'off', 20 | 'react/jsx-props-no-spreading': 'off', 21 | 'no-underscore-dangle': 'off', 22 | 'prettier/prettier': 'off', 23 | 'react/destructuring-assignment': 'off', // Vscode doesn't support automatic destructuring, it's a pain to add a new variable 24 | 'jsx-a11y/anchor-is-valid': 'off', // Next.js use his own internal link system 25 | 'react/require-default-props': 'off', // Allow non-defined react props as undefined 26 | '@next/next/no-img-element': 'off', // We currently not using next/image because it isn't supported with SSG mode 27 | 'import/prefer-default-export': 'off', 28 | '@typescript-eslint/no-unused-vars': 'off', 29 | 'jsx-a11y/click-events-have-key-events': 'off', 30 | 'unused-imports/no-unused-imports': 'error', 31 | 'unused-imports/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 32 | 'no-param-reassign': ['error', { props: false }], 33 | 'no-else-return': ['error', { allowElseIf: true }], 34 | "@next/next/no-page-custom-font": "off" 35 | }, 36 | }, 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /apps/playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Configuration for JavaScript files 3 | extends: ['airbnb-base', 'plugin:prettier/recommended'], 4 | rules: { 5 | 'prettier/prettier': 'off', 6 | }, 7 | overrides: [ 8 | // Configuration for TypeScript files 9 | { 10 | files: ['**/*.ts', '**/*.tsx'], 11 | plugins: ['@typescript-eslint', 'unused-imports'], 12 | extends: ['airbnb-typescript', 'next', 'next/core-web-vitals', 'plugin:prettier/recommended'], 13 | parserOptions: { 14 | project: './tsconfig.json', 15 | tsconfigRootDir: __dirname, 16 | }, 17 | rules: { 18 | 'one-var': 'off', 19 | 'prefer-destructuring': 'off', 20 | 'react/jsx-props-no-spreading': 'off', 21 | 'no-underscore-dangle': 'off', 22 | 'prettier/prettier': 'off', 23 | 'react/destructuring-assignment': 'off', // Vscode doesn't support automatic destructuring, it's a pain to add a new variable 24 | 'jsx-a11y/anchor-is-valid': 'off', // Next.js use his own internal link system 25 | 'react/require-default-props': 'off', // Allow non-defined react props as undefined 26 | '@next/next/no-img-element': 'off', // We currently not using next/image because it isn't supported with SSG mode 27 | 'import/prefer-default-export': 'off', 28 | '@typescript-eslint/no-unused-vars': 'off', 29 | 'jsx-a11y/click-events-have-key-events': 'off', 30 | 'unused-imports/no-unused-imports': 'error', 31 | 'unused-imports/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 32 | 'no-param-reassign': ['error', { props: false }], 33 | 'no-else-return': ['error', { allowElseIf: true }], 34 | '@next/next/no-page-custom-font': 'off', 35 | }, 36 | }, 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /packages/testing/tests/generateUniversalLanguage.test.ts: -------------------------------------------------------------------------------- 1 | import { generateUniversalLanguage } from '../libs/utils/generateUniversalLanguage'; 2 | 3 | describe('generateUniversalLanguage', () => { 4 | it(`Generate all string combinations`, () => { 5 | expect(generateUniversalLanguage(['a', 'b', 'c'], 3, 2)).toStrictEqual([ 6 | ['a', 'a'], 7 | ['a', 'b'], 8 | ['a', 'c'], 9 | ['b', 'a'], 10 | ['b', 'b'], 11 | ['b', 'c'], 12 | ['c', 'a'], 13 | ['c', 'b'], 14 | ['c', 'c'], 15 | ['a', 'a', 'a'], 16 | ['a', 'a', 'b'], 17 | ['a', 'a', 'c'], 18 | ['a', 'b', 'a'], 19 | ['a', 'b', 'b'], 20 | ['a', 'b', 'c'], 21 | ['a', 'c', 'a'], 22 | ['a', 'c', 'b'], 23 | ['a', 'c', 'c'], 24 | ['b', 'a', 'a'], 25 | ['b', 'a', 'b'], 26 | ['b', 'a', 'c'], 27 | ['b', 'b', 'a'], 28 | ['b', 'b', 'b'], 29 | ['b', 'b', 'c'], 30 | ['b', 'c', 'a'], 31 | ['b', 'c', 'b'], 32 | ['b', 'c', 'c'], 33 | ['c', 'a', 'a'], 34 | ['c', 'a', 'b'], 35 | ['c', 'a', 'c'], 36 | ['c', 'b', 'a'], 37 | ['c', 'b', 'b'], 38 | ['c', 'b', 'c'], 39 | ['c', 'c', 'a'], 40 | ['c', 'c', 'b'], 41 | ['c', 'c', 'c'], 42 | ]); 43 | }); 44 | }); 45 | 46 | it(`Should generate string combinations when callback is present`, () => { 47 | const mockFn = jest.fn(); 48 | expect(generateUniversalLanguage(['a', 'b'], 2, undefined, mockFn)).toStrictEqual([ 49 | ['a'], 50 | ['b'], 51 | ['a', 'a'], 52 | ['a', 'b'], 53 | ['b', 'a'], 54 | ['b', 'b'], 55 | ]); 56 | 57 | expect(mockFn.mock.calls).toEqual([ 58 | [['a']], 59 | [['b']], 60 | [['a', 'a']], 61 | [['a', 'b']], 62 | [['b', 'a']], 63 | [['b', 'b']], 64 | ]); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/regex/libs/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | 3 | export type UnaryRegexOperators = "+" | "*" | "?"; 4 | export type UnaryRegexOperatorsName = "Plus" | "Kleene" | "Optional"; 5 | export type BinaryRegexOperators = "." | "|"; 6 | export type BinaryRegexOperatorsName = "Concat" | "Or"; 7 | 8 | export interface UnaryRegexNode { 9 | operator: Operator 10 | operands: [RegexNode]; 11 | } 12 | 13 | export interface BinaryRegexNode { 14 | operator: Operator 15 | operands: [RegexNode, RegexNode]; 16 | } 17 | 18 | export interface ConcatRegexNode extends BinaryRegexNode<"Concat"> {} 19 | export interface OrRegexNode extends BinaryRegexNode<"Or"> {} 20 | export interface KleeneRegexNode extends UnaryRegexNode<"Kleene"> {} 21 | export interface OptionalRegexNode extends UnaryRegexNode<"Optional"> {} 22 | export interface PlusRegexNode extends UnaryRegexNode<"Plus"> {} 23 | 24 | export interface LiteralRegexNode { 25 | operator: 'Literal'; 26 | operands: [string | number]; 27 | } 28 | 29 | export interface IRegularExpression { 30 | alphabets: string[]; 31 | regex: RegExp; 32 | label: string; 33 | description?: string; 34 | } 35 | 36 | export type RegexNode = 37 | | ConcatRegexNode 38 | | KleeneRegexNode 39 | | OptionalRegexNode 40 | | OrRegexNode 41 | | LiteralRegexNode 42 | | PlusRegexNode; 43 | 44 | // eslint-disable-next-line 45 | export type IAutomatonTestLogicFn = (inputString: string, automatonTestResult: boolean) => boolean; 46 | // eslint-disable-next-line 47 | export type IAutomatonTestFn = (inputString: string) => boolean; 48 | -------------------------------------------------------------------------------- /packages/cfg/libs/generateParseTreeFromDerivations.ts: -------------------------------------------------------------------------------- 1 | import { Derivation, ParseTree } from "./types"; 2 | 3 | /** 4 | * Generate parse tree from derivations 5 | * @param variables Array of variables string 6 | * @param derivations Array of production variable and tokens tuple 7 | * @returns Parse tree 8 | */ 9 | export function generateParseTreeFromDerivations(variables: string[], derivations: Derivation[]) { 10 | const rootTree: ParseTree[] = [] 11 | const variablesSet = new Set(variables); 12 | 13 | // Global state to keep track of which derivation we are currently at 14 | let derivationNumber = 0; 15 | 16 | function recurse(derivation: Derivation, childContainer: (string | ParseTree)[]) { 17 | const [productionVariable, productionRuleTokens] = derivation; 18 | // Create the parent node 19 | const parentNode: ParseTree = { 20 | [productionVariable]: [] 21 | }; 22 | 23 | // Loop through all the tokens 24 | for (let index = 0; index < productionRuleTokens.length; index+=1) { 25 | const token = productionRuleTokens[index]; 26 | // Check if the token is a variable 27 | if (variablesSet.has(token)) { 28 | // Move to the next derivation 29 | derivationNumber += 1; 30 | // Make sure we haven't exhausted the derivations 31 | if (derivationNumber < derivations.length) { 32 | recurse(derivations[derivationNumber], parentNode[productionVariable]) 33 | } else { 34 | break; 35 | } 36 | } else { 37 | // Always push terminals as is 38 | parentNode[productionVariable].push(token) 39 | } 40 | } 41 | childContainer.push(parentNode) 42 | } 43 | 44 | recurse(derivations[0], rootTree) 45 | return rootTree[0] 46 | } -------------------------------------------------------------------------------- /packages/fa/libs/NonDeterministicFiniteAutomaton/convertToRegularNfa.ts: -------------------------------------------------------------------------------- 1 | import { TransformedFiniteAutomaton } from '../types'; 2 | import { epsilonClosureOfState } from './epsilonClosureOfState'; 3 | import { moveAndEpsilonClosureStateSet } from './moveAndEpsilonClosureStateSet'; 4 | 5 | /** 6 | * Convert an epsilon-nfa to nfa 7 | * @param automaton epsilon-nfa to be converted 8 | */ 9 | export function convertToRegularNfa( 10 | automaton: Pick< 11 | TransformedFiniteAutomaton, 12 | 'transitions' | 'epsilon_transitions' | 'alphabets' | 'states' 13 | > 14 | ) { 15 | const { transitions, states, alphabets, epsilon_transitions: epsilonTransitions } = automaton; 16 | const epsilonClosureOfStateCache: Record = {}; 17 | alphabets.forEach((symbol) => { 18 | states.forEach((state) => { 19 | // Only if the state has a key on the epsilon transition record, we are gonna expand it 20 | if (epsilonTransitions && epsilonTransitions[state]) { 21 | const epsilonClosuredStates = !epsilonClosureOfStateCache[state] 22 | ? epsilonClosureOfState(automaton.epsilon_transitions, state) 23 | : epsilonClosureOfStateCache[state]; 24 | if (!epsilonClosureOfStateCache[state]) { 25 | epsilonClosureOfStateCache[state] = epsilonClosuredStates; 26 | } 27 | 28 | const epsilonClosuredStatesForSymbol = moveAndEpsilonClosureStateSet( 29 | transitions, 30 | epsilonTransitions, 31 | Array.from(epsilonClosuredStates), 32 | symbol 33 | ); 34 | if (transitions[state]) { 35 | transitions[state][symbol] = epsilonClosuredStatesForSymbol; 36 | } else { 37 | transitions[state] = { 38 | [symbol]: epsilonClosuredStatesForSymbol, 39 | }; 40 | } 41 | } 42 | }); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /apps/landing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-js-boilerplate", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "build-stats": "cross-env ANALYZE=true npm run build", 9 | "export": "next export", 10 | "build-prod": "run-s clean build export", 11 | "clean": "rimraf .next out", 12 | "lint": "next lint", 13 | "build-types": "tsc --noEmit --pretty" 14 | }, 15 | "dependencies": { 16 | "@fauton/cfg": "0.0.2", 17 | "next": "^12.1.0", 18 | "next-seo": "^4.28.1", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "styled-jsx-plugin-postcss": "^4.0.1" 22 | }, 23 | "devDependencies": { 24 | "@next/bundle-analyzer": "^12.0.2", 25 | "@types/node": "^16.11.6", 26 | "@types/react": "^17.0.33", 27 | "@typescript-eslint/eslint-plugin": "^4.33.0", 28 | "@typescript-eslint/parser": "^4.33.0", 29 | "autoprefixer": "^10.4.0", 30 | "cross-env": "^7.0.3", 31 | "eslint": "^7.32.0", 32 | "eslint-config-airbnb-base": "^14.2.1", 33 | "eslint-config-airbnb-typescript": "^14.0.1", 34 | "eslint-config-next": "^12.0.2", 35 | "eslint-config-prettier": "^8.3.0", 36 | "eslint-plugin-import": "^2.25.2", 37 | "eslint-plugin-jsx-a11y": "^6.4.1", 38 | "eslint-plugin-prettier": "^4.0.0", 39 | "eslint-plugin-react": "^7.26.1", 40 | "eslint-plugin-react-hooks": "^4.2.0", 41 | "eslint-plugin-unused-imports": "^1.1.5", 42 | "husky": "^7.0.4", 43 | "lint-staged": "^11.2.6", 44 | "npm-run-all": "^4.1.5", 45 | "postcss": "^8.3.11", 46 | "prettier": "^2.4.1", 47 | "rimraf": "^3.0.2", 48 | "tailwindcss": "^2.2.19", 49 | "typescript": "^4.4.4" 50 | }, 51 | "license": "ISC" 52 | } -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures.tsx: -------------------------------------------------------------------------------- 1 | import useBaseUrl from '@docusaurus/useBaseUrl'; 2 | import clsx from 'clsx'; 3 | import React from 'react'; 4 | import styles from './HomepageFeatures.module.css'; 5 | 6 | type FeatureItem = { 7 | title: string; 8 | image: string; 9 | description: JSX.Element; 10 | }; 11 | 12 | const FeatureList: FeatureItem[] = [ 13 | { 14 | title: 'Easy to Use', 15 | image: '/img/easy.svg', 16 | description: ( 17 | <> 18 | All the packages are well documented and crafted with ease of use in mind 19 | 20 | ), 21 | }, 22 | { 23 | title: 'Multi Purpose', 24 | image: '/img/multi_purpose.svg', 25 | description: ( 26 | <> 27 | Fauton's ecosystem provides various packages to do almost anything with automaton 28 | 29 | ), 30 | }, 31 | { 32 | title: 'Typescript Support', 33 | image: '/img/ts_support.svg', 34 | description: ( 35 | <> 36 | Typescript support right out of the box for static typechecking. 37 | 38 | ), 39 | }, 40 | ]; 41 | 42 | function Feature({ image, title, description }: FeatureItem) { 43 | const imgUrl = useBaseUrl(image); 44 | return ( 45 |
46 | {title} 47 |

{title}

48 |

{description}

49 |
50 | ); 51 | } 52 | 53 | export default function HomepageFeatures(): JSX.Element { 54 | return ( 55 |
56 |
57 |
58 | {FeatureList.map((props, idx) => ( 59 | 60 | ))} 61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /packages/cfg/tests/validateCfg.test.ts: -------------------------------------------------------------------------------- 1 | import { validateCfg } from '../libs/validateCfg'; 2 | 3 | it(`Should throw error if transition record contains a variable that is not present in variables array`, () => { 4 | try { 5 | validateCfg({ 6 | productionRules: { 7 | S: ['a'], 8 | }, 9 | startVariable: 'S', 10 | terminals: [], 11 | variables: ['A'], 12 | }); 13 | } catch (err) { 14 | expect(err.message).toBe( 15 | `Transition record contains a variable S, that is not present in variables array` 16 | ); 17 | } 18 | }); 19 | 20 | it(`Should throw error if transition record substitution chunk is neither a variable nor a terminal`, () => { 21 | try { 22 | validateCfg({ 23 | productionRules: { 24 | A: ['a'], 25 | }, 26 | startVariable: 'S', 27 | terminals: ['b'], 28 | variables: ['A'], 29 | }); 30 | } catch (err) { 31 | expect(err.message).toBe( 32 | `Transition record substitution chunk a is neither a variable nor a terminal` 33 | ); 34 | } 35 | }); 36 | 37 | it(`Should throw error if all variables are not present in transition record`, () => { 38 | try { 39 | validateCfg({ 40 | productionRules: { 41 | A: ['a'], 42 | B: ['a'], 43 | }, 44 | startVariable: 'S', 45 | terminals: ['b'], 46 | variables: ['B'], 47 | }); 48 | } catch (err) { 49 | expect(err.message).toBe(`All variables must be present in the transition record`); 50 | } 51 | }); 52 | 53 | it(`Should throw error if starting variable is not part of variables array`, () => { 54 | try { 55 | validateCfg({ 56 | productionRules: { 57 | A: ['a'], 58 | B: ['a'], 59 | }, 60 | startVariable: 'S', 61 | terminals: ['a'], 62 | variables: ['B', 'A'], 63 | }); 64 | } catch (err) { 65 | expect(err.message).toBe(`Starting variable must be part of variables array`); 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /packages/fa/libs/DeterministicFiniteAutomaton/generateEquivalenceStates.ts: -------------------------------------------------------------------------------- 1 | import { TransformedFiniteAutomaton } from '../types'; 2 | import { generateStateGroupsRecord } from './generateStateGroupsRecord'; 3 | 4 | export function generateEquivalenceStates( 5 | automaton: Pick, 6 | stateGroups: string[][] 7 | ) { 8 | const stateGroupsRecord = generateStateGroupsRecord(automaton.states, stateGroups); 9 | const stateGroupsSymbolsRecord: Record = {}; 10 | // Segregating state groups based on its length 11 | const singleStateGroups: string[][] = []; 12 | const compositeStateGroups: string[][] = []; 13 | stateGroups.forEach((stateGroup) => { 14 | if (stateGroup.length > 1) { 15 | compositeStateGroups.push(stateGroup); 16 | } else if (stateGroup.length !== 0) { 17 | singleStateGroups.push(stateGroup); 18 | } 19 | }); 20 | // Looping through only composite state groups as only they can be broken down further 21 | compositeStateGroups.forEach((compositeStateGroup) => { 22 | compositeStateGroup.forEach((state) => { 23 | // Each combination of state group (for each symbol) we will be the key 24 | let stateGroup = ''; 25 | automaton.alphabets.forEach((symbol) => { 26 | stateGroup += stateGroupsRecord[automaton.transitions[state][symbol].toString()]; 27 | }); 28 | if (!stateGroupsSymbolsRecord[stateGroup]) { 29 | stateGroupsSymbolsRecord[stateGroup] = [state]; 30 | } else { 31 | stateGroupsSymbolsRecord[stateGroup].push(state); 32 | } 33 | }); 34 | }); 35 | 36 | const statesGroups = Object.values(stateGroupsSymbolsRecord); 37 | // Attaching the single state groups as they were not present in the record 38 | if (singleStateGroups.length !== 0) { 39 | return statesGroups.concat(singleStateGroups); 40 | } else { 41 | // Don't concat single state 42 | return statesGroups; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-js-boilerplate", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "build-stats": "cross-env ANALYZE=true npm run build", 9 | "export": "next export", 10 | "build-prod": "run-s clean build export", 11 | "clean": "rimraf .next out", 12 | "lint": "next lint", 13 | "build-types": "tsc --noEmit --pretty" 14 | }, 15 | "dependencies": { 16 | "@emotion/react": "^11.7.1", 17 | "@emotion/styled": "^11.6.0", 18 | "@fauton/cfg": "0.0.4", 19 | "@mui/icons-material": "^5.2.5", 20 | "@mui/material": "^5.2.5", 21 | "@types/tailwindcss": "^2.2.4", 22 | "next": "^12.1.0", 23 | "next-seo": "^4.28.1", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2", 26 | "styled-jsx-plugin-postcss": "^4.0.1" 27 | }, 28 | "devDependencies": { 29 | "@next/bundle-analyzer": "^12.0.2", 30 | "@types/node": "^16.11.6", 31 | "@types/react": "^17.0.33", 32 | "@typescript-eslint/eslint-plugin": "^4.33.0", 33 | "@typescript-eslint/parser": "^4.33.0", 34 | "autoprefixer": "^10.4.0", 35 | "cross-env": "^7.0.3", 36 | "eslint": "^7.32.0", 37 | "eslint-config-airbnb-base": "^14.2.1", 38 | "eslint-config-airbnb-typescript": "^14.0.1", 39 | "eslint-config-next": "^12.0.2", 40 | "eslint-config-prettier": "^8.3.0", 41 | "eslint-plugin-import": "^2.25.2", 42 | "eslint-plugin-jsx-a11y": "^6.4.1", 43 | "eslint-plugin-prettier": "^4.0.0", 44 | "eslint-plugin-react": "^7.26.1", 45 | "eslint-plugin-react-hooks": "^4.2.0", 46 | "eslint-plugin-unused-imports": "^1.1.5", 47 | "husky": "^7.0.4", 48 | "lint-staged": "^11.2.6", 49 | "npm-run-all": "^4.1.5", 50 | "postcss": "^8.3.11", 51 | "prettier": "^2.4.1", 52 | "rimraf": "^3.0.2", 53 | "tailwindcss": "^2.2.19", 54 | "typescript": "^4.4.4" 55 | }, 56 | "license": "ISC" 57 | } -------------------------------------------------------------------------------- /packages/testing/libs/utils/generateUniversalLanguage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates all combination of strings that can be using the tokens from length 1 to `maxTokenLength` 3 | * @param tokens Alphabet of the strings 4 | * @param maxTokenLength Highest number of token the generated string can have 5 | * @param startTokenLength Starting token length of the string 6 | * @param languageChecker A cb passed each generated string 7 | * @returns An array of strings 8 | */ 9 | export function generateUniversalLanguage( 10 | tokens: string[], 11 | maxTokenLength: number, 12 | startTokenLength?: number, 13 | // eslint-disable-next-line 14 | cb?: (inputTokens: string[]) => void 15 | ) { 16 | // An array to store all the generated strings 17 | // there is no point in using a set for two reasons 18 | // 1. Each string would be unique, the algorithm ensures that, ofc depending on the token 19 | // 2. Set would throw an error if we store huge number of items in it 20 | const generatedStrings: Array> = []; 21 | 22 | // Recursive function to generate all possible combinations 23 | function generateAllKLength(generatedTokens: string[], tokensLength: number) { 24 | // Base case, if we dont have any more token to use 25 | // Call the callback and pass the generated token 26 | if (tokensLength === 0) { 27 | if (cb) { 28 | cb(generatedTokens); 29 | } 30 | // Push the generated tokens to the array 31 | generatedStrings.push(generatedTokens); 32 | return; 33 | } 34 | // Loop through all the tokens and recursively call the function decreasing the token length 35 | for (let i = 0; i < tokens.length; i += 1) { 36 | generateAllKLength(generatedTokens.concat(tokens[i]), tokensLength - 1); 37 | } 38 | } 39 | 40 | // Generate all possible combinations of token from $startTokenLength to $maxTokenLength 41 | for (let length = startTokenLength ?? 1; length <= maxTokenLength; length += 1) { 42 | generateAllKLength([], length); 43 | } 44 | 45 | return generatedStrings; 46 | } 47 | -------------------------------------------------------------------------------- /packages/cfg/libs/generateVariableReferenceRecord.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar } from "./types"; 2 | 3 | export interface VariableReferenceLocation { 4 | // The production variable name 5 | variable: string, 6 | // Rule number of the production variable 7 | ruleNumber: number, 8 | // Token number of the rule 9 | tokenNumber: number 10 | } 11 | 12 | /** 13 | * Return a record providing details of the location of each variable of the cfg 14 | * @param cfg Input context free grammar 15 | * @returns A record which provides info regarding the location of a variable 16 | */ 17 | export default function generateVariableReferenceRecord(cfg: Pick) { 18 | const {variables, productionRules} = cfg; 19 | const variableReferenceLocationRecord: Record = {}; 20 | 21 | // Loop through all variables 22 | variables.forEach(targetVariable => { 23 | // Array to keep track of all the locations outer variable is being referenced 24 | const variableReferences: VariableReferenceLocation[] = [] 25 | // Loop though all variables again 26 | variables.forEach(productionVariable => { 27 | // Get the rules of the inner variable 28 | const rules = productionRules[productionVariable]; 29 | // Loop through each rule 30 | rules.forEach((rule, ruleNumber) => { 31 | // Get the tokens of the rule 32 | const tokens = rule.split(" "); 33 | // Loop through all token 34 | tokens.forEach((token, tokenNumber) => { 35 | // Check if the token is the same as outer variable 36 | if (token === targetVariable) { 37 | variableReferences.push({ 38 | variable: productionVariable, 39 | ruleNumber, 40 | tokenNumber 41 | }) 42 | } 43 | }) 44 | }) 45 | }) 46 | variableReferenceLocationRecord[targetVariable] = variableReferences; 47 | }) 48 | 49 | return variableReferenceLocationRecord; 50 | } -------------------------------------------------------------------------------- /apps/playground/src/components/Select.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { MenuItem, Select as MuiSelect, SelectProps as MuiSelectProps } from "@mui/material"; 3 | import { ReactNode, SelectHTMLAttributes } from "react"; 4 | 5 | interface SelectProps { 6 | value: SelectHTMLAttributes["value"], 7 | onChange: MuiSelectProps["onChange"], 8 | valueLabelRecord: Record 9 | renderValue?: (value: string | number) => ReactNode 10 | className?: string 11 | menuItemRender?: (item: string | number) => ReactNode 12 | } 13 | 14 | const StyledMuiSelect = styled(MuiSelect)` 15 | padding-left: ${({theme}) => theme.spacing(0.5)}; 16 | border-radius: ${({theme}) => theme.spacing(0.5)}; 17 | font-weight: bold; 18 | `; 19 | 20 | const SelectRenderedValue = styled.div` 21 | font-weight: semi-bold; 22 | text-transform: capitalize; 23 | border-radius: ${({theme}) => theme.spacing(0.5)}; 24 | ` 25 | 26 | export function Select(props: SelectProps) { 27 | const { menuItemRender, renderValue, className = "", valueLabelRecord, value, onChange } = props; 28 | const items = Object.entries(valueLabelRecord); 29 | return 33 | ( 34 | 35 | {renderValue ? renderValue(valueToRender as string) : valueLabelRecord[valueToRender as string]} 36 | 37 | ) 38 | } 39 | disabled={items.length === 0} 40 | sx={{ 41 | "& .MuiSvgIcon-root": { 42 | fill: `white` 43 | }, 44 | }} 45 | onChange={onChange} 46 | > 47 | {items.length !== 0 ? items.map(([itemValue, itemLabel]) => ( 48 | 55 | {menuItemRender ? menuItemRender(itemValue) : itemLabel} 56 | 57 | )) : null} 58 | 59 | } -------------------------------------------------------------------------------- /packages/cfg/tests/cykParse.test.ts: -------------------------------------------------------------------------------- 1 | import { cykParse } from '../libs/cykParse'; 2 | 3 | describe('cykParse', () => { 4 | it(`Basic CYK parsing`, () => { 5 | const cykParseResult = cykParse( 6 | { 7 | startVariable: 'S', 8 | productionRules: { 9 | S: ['A B', 'B'], 10 | A: ['B A', 'a'], 11 | B: ['A', 'b'], 12 | }, 13 | }, 14 | 'b a b'.split(' ') 15 | ); 16 | expect(cykParseResult).toStrictEqual({ 17 | verdict: true, 18 | cykTableDetailed: [ 19 | [ 20 | { 21 | combinations: [ 22 | { 23 | merged: ['A B'], 24 | parts: [['A'], ['B']], 25 | }, 26 | { 27 | merged: ['B S'], 28 | parts: [['B'], ['S']], 29 | }, 30 | ], 31 | value: ['S'], 32 | }, 33 | ], 34 | [ 35 | { 36 | combinations: [ 37 | { 38 | merged: ['B A'], 39 | parts: [['B'], ['A']], 40 | }, 41 | ], 42 | value: ['A'], 43 | }, 44 | { 45 | combinations: [ 46 | { 47 | merged: ['A B'], 48 | parts: [['A'], ['B']], 49 | }, 50 | ], 51 | value: ['S'], 52 | }, 53 | ], 54 | [ 55 | { 56 | combinations: [ 57 | { 58 | merged: ['B'], 59 | parts: [['B']], 60 | }, 61 | ], 62 | value: ['B'], 63 | }, 64 | { 65 | combinations: [ 66 | { 67 | merged: ['A'], 68 | parts: [['A']], 69 | }, 70 | ], 71 | value: ['A'], 72 | }, 73 | { 74 | combinations: [ 75 | { 76 | merged: ['B'], 77 | parts: [['B']], 78 | }, 79 | ], 80 | value: ['B'], 81 | }, 82 | ], 83 | ], 84 | cykTable: [['S'], ['A', 'S'], ['B', 'A', 'B']], 85 | nodeVariablesRecord: { 86 | 'A B': ['S'], 87 | B: ['S'], 88 | 'B A': ['A'], 89 | a: ['A'], 90 | A: ['B'], 91 | b: ['B'], 92 | }, 93 | sentenceTokens: ['b', 'a', 'b'], 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/cfg/libs/validateCfg.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar } from './types'; 2 | 3 | /** 4 | * Validates a cfg 5 | * @param cfg Input cfg to validate 6 | */ 7 | export function validateCfg(cfg: IContextFreeGrammar) { 8 | const { startVariable, terminals, productionRules, variables } = cfg; 9 | // Check if all the variables is present in transition record 10 | const productionRulesEntries = Object.entries(productionRules); 11 | if (productionRulesEntries.length !== variables.length) { 12 | throw new Error('All variables must be present in the transition record'); 13 | } 14 | const terminalsSet = new Set(terminals); 15 | const variablesSet = new Set(variables); 16 | // Validate that all the keys of transition record are variables 17 | productionRulesEntries.forEach(([productionRuleVariable, productionRuleSubstitutions]) => { 18 | if (!variablesSet.has(productionRuleVariable)) { 19 | throw new Error( 20 | `Transition record contains a variable ${productionRuleVariable}, that is not present in variables array` 21 | ); 22 | } 23 | // Check if all the substitutions contain either variable or terminal 24 | productionRuleSubstitutions.forEach((productionRuleSubstitution) => { 25 | const tokens = productionRuleSubstitution.split(' '); 26 | for (let index = 0; index < tokens.length; index += 1) { 27 | const productionRuleSubstitutionToken = tokens[index]; 28 | // Check if the letter is a terminal 29 | const isTerminal = terminalsSet.has(productionRuleSubstitutionToken); 30 | // Check if the letter is a variable 31 | const isVariable = variablesSet.has(productionRuleSubstitutionToken); 32 | 33 | if (!isTerminal && !isVariable) { 34 | throw new Error( 35 | `Transition record substitution chunk ${productionRuleSubstitutionToken} is neither a variable nor a terminal` 36 | ); 37 | } 38 | } 39 | }); 40 | }); 41 | // Check if the starting variable is part of variables 42 | if (!variablesSet.has(startVariable)) { 43 | throw new Error(`Starting variable must be part of variables array`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/fa/tests/FiniteAutomaton/generatePostNormalizationErrors.test.ts: -------------------------------------------------------------------------------- 1 | import { generatePostNormalizationErrors } from '../../libs/FiniteAutomaton/generatePostNormalizationErrors'; 2 | 3 | describe('generatePostNormalizationErrors', () => { 4 | it(`Using valid dfa`, () => { 5 | const postNormalizationErrors = generatePostNormalizationErrors({ 6 | alphabets: ['a', 'b'], 7 | final_states: ['0', '1'], 8 | label: 'DFA', 9 | start_state: '0', 10 | states: ['0', '1', '2'], 11 | transitions: { 12 | 0: { 13 | a: ['1'], 14 | b: ['2'], 15 | }, 16 | 1: { 17 | a: ['2'], 18 | b: ['1'], 19 | }, 20 | 2: { 21 | a: ['2'], 22 | b: ['2'], 23 | }, 24 | }, 25 | epsilon_transitions: null, 26 | }); 27 | expect(postNormalizationErrors.length).toStrictEqual(0); 28 | }); 29 | 30 | it(`Using invalid dfa`, () => { 31 | const postNormalizationErrors = generatePostNormalizationErrors({ 32 | alphabets: ['a', 'b'], 33 | final_states: ['0', '3'], 34 | label: 'DFA', 35 | start_state: '0', 36 | states: ['0', '1', '2'], 37 | transitions: { 38 | 0: { 39 | a: ['1'], 40 | b: ['2'], 41 | }, 42 | 1: { 43 | a: ['2'], 44 | b: ['1'], 45 | }, 46 | 2: { 47 | a: ['2'], 48 | b: ['2'], 49 | }, 50 | 3: { 51 | c: ['4'], 52 | }, 53 | }, 54 | epsilon_transitions: { 55 | 1: ['2'], 56 | 2: ['0', '3'], 57 | 3: ['1'], 58 | }, 59 | }); 60 | expect(postNormalizationErrors).toStrictEqual([ 61 | `Automaton final_states must reference a state (3) that is present in states`, 62 | `Epsilon transitions state 2 must reference a state 3 that is present in states`, 63 | `Epsilon transitions state 3 must reference a state that is present in states`, 64 | `Automaton transitions (3) must reference a state that is present in states`, 65 | `Automaton transitions symbol (c), must reference a valid alphabet`, 66 | `Automaton transitions value (4) when a tuple, must reference a valid state`, 67 | ]); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/testing/libs/utils/generateRandomLanguage.ts: -------------------------------------------------------------------------------- 1 | import { generateRandomNumber } from './generateRandomNumber'; 2 | 3 | /** 4 | * Generates an array of unique random strings over a given tokens 5 | * @param total Total unique random strings 6 | * @param tokens Alphabet of the random strings 7 | * @param minTokenLength Minimum length of each string 8 | * @param maxTokenLength Maximum length of each string 9 | * @param initialInputStrings Initial array of strings 10 | * @returns An array of unique random strings 11 | */ 12 | export function generateRandomLanguage( 13 | total: number, 14 | tokens: string[], 15 | minTokenLength: number, 16 | maxTokenLength: number, 17 | initialInputStrings?: string[][] 18 | ) { 19 | // Using a set to store only unique input strings 20 | const generatedStringsSet: Set = new Set( 21 | initialInputStrings?.map((initialInputString) => initialInputString.join(' ')) ?? [] 22 | ); 23 | 24 | // WARN This could potentially cause an infinite loop, 25 | // While we haven't reached total tokens equal to total 26 | while (generatedStringsSet.size < total) { 27 | // Generate a random number for the token length 28 | const tokenLength = generateRandomNumber(minTokenLength, maxTokenLength); 29 | const generatedString: string[] = []; 30 | for (let index = 0; index < tokenLength; index += 1) { 31 | // Generate a random token 32 | generatedString.push(tokens[generateRandomNumber(0, tokens.length - 1)]); 33 | } 34 | // Add the generated string by joining the tokens with space 35 | generatedStringsSet.add(generatedString.join(' ')); 36 | } 37 | // Using a array of tokens, rather than a set as there is a limit to the number of items a set can contain 38 | // If $total is a very large number using a set would throw an error 39 | const generatedStrings: string[][] = []; 40 | // Convert the set to an array 41 | Array.from(generatedStringsSet).forEach((generatedString) => { 42 | // Generate tokens from the string by splitting it via space 43 | generatedStrings.push(generatedString.split(' ')); 44 | }); 45 | return generatedStrings; 46 | } 47 | -------------------------------------------------------------------------------- /packages/cfg/libs/index.ts: -------------------------------------------------------------------------------- 1 | import { convertGrammarToString } from './convertGrammarToString'; 2 | import { convertStringToGrammar } from './convertStringToGrammar'; 3 | import { convertToCnf } from './convertToCnf'; 4 | import { cykParse } from './cykParse'; 5 | import { extractTerminalsFromCfg } from './extractTerminalsFromCfg'; 6 | import { findFirst } from './findFirst'; 7 | import { findFollow } from './findFollow'; 8 | import { generateCfgLanguage } from './generateCfgLanguage'; 9 | import { generateLL1ParsingTable } from './generateLL1ParsingTable'; 10 | import { addDotToProductionRule, generateClosureOfLR0Item, generateLR0ParsingTable } from './generateLR0ParsingTable'; 11 | import { parseWithLL1Table } from "./parseWithLL1Table"; 12 | import { removeEmptyProduction } from './removeEmptyProduction'; 13 | import { removeLeftRecursion } from './removeLeftRecursion'; 14 | import { removeNonDeterminism } from './removeNonDeterminism'; 15 | import { removeNonTerminableProduction } from './removeNonTerminableProduction'; 16 | import { removeNullProduction } from './removeNullProduction'; 17 | import { removeUnitProduction } from './removeUnitProduction'; 18 | import { removeUnreachableProduction } from './removeUnreachableProduction'; 19 | import { removeUselessProduction } from './removeUselessProduction'; 20 | import { simplifyCfg } from './simplifyCfg'; 21 | import { validateCfg } from './validateCfg'; 22 | 23 | // TODO: Create separate classes for grouping modules 24 | // Name-spacing lr0 parser related modules 25 | export const LRO = { 26 | addDotToProductionRule, 27 | generateClosureOfLR0Item, 28 | generateLR0ParsingTable, 29 | } 30 | 31 | export * from './types'; 32 | export { 33 | removeLeftRecursion, 34 | removeNonDeterminism, 35 | parseWithLL1Table, 36 | generateLL1ParsingTable, 37 | findFollow, 38 | convertGrammarToString, 39 | convertStringToGrammar, 40 | convertToCnf, 41 | cykParse, 42 | generateCfgLanguage, 43 | removeEmptyProduction, 44 | removeNonTerminableProduction, 45 | removeNullProduction, 46 | removeUnitProduction, 47 | removeUnreachableProduction, 48 | removeUselessProduction, 49 | extractTerminalsFromCfg, 50 | simplifyCfg, 51 | validateCfg, 52 | findFirst 53 | }; 54 | 55 | -------------------------------------------------------------------------------- /packages/fa/tests/FiniteAutomaton/generatePreNormalizationErrors.test.ts: -------------------------------------------------------------------------------- 1 | import { generatePreNormalizationErrors } from '../../libs/FiniteAutomaton/generatePreNormalizationErrors'; 2 | 3 | describe('generatePreNormalizationErrors', () => { 4 | it(`Using valid dfa`, () => { 5 | const preNormalizationErrors = generatePreNormalizationErrors(() => true, 'deterministic', { 6 | alphabets: ['a', 'b'], 7 | final_states: [0, 1], 8 | label: 'DFA', 9 | start_state: 0, 10 | states: [0, 1, 2], 11 | transitions: { 12 | 0: [1, 2], 13 | 1: [2, 1], 14 | 2: 'loop', 15 | }, 16 | }); 17 | expect(preNormalizationErrors.length).toBe(0); 18 | }); 19 | 20 | it(`Using invalid dfa without any properties`, () => { 21 | const preNormalizationErrors = generatePreNormalizationErrors( 22 | undefined as any, 23 | 'deterministic', 24 | { 25 | epsilon_transitions: {}, 26 | } as any 27 | ); 28 | expect(preNormalizationErrors).toStrictEqual([ 29 | `testLogic function is required in automaton`, 30 | 'Automaton label is required', 31 | 'Automaton transitions is required', 32 | 'Automaton states is required', 33 | 'Automaton alphabets is required', 34 | `Deterministic automaton can't contain epsilon transitions`, 35 | 'Automaton start_state is required', 36 | 'Automaton final_states is required', 37 | 'Automaton states must be an array of length > 0', 38 | 'Automaton alphabets must be an array of length > 0', 39 | 'Automaton final_states must be an array of length > 0', 40 | ]); 41 | }); 42 | 43 | it(`Using invalid dfa with properties`, () => { 44 | const preNormalizationErrors = generatePreNormalizationErrors(() => true, 'deterministic', { 45 | states: 5, 46 | alphabets: 5, 47 | final_states: 5, 48 | start_state: 0, 49 | label: 'DFA', 50 | transitions: {}, 51 | } as any); 52 | expect(preNormalizationErrors).toStrictEqual([ 53 | 'Automaton alphabets must be an array', 54 | 'Automaton states must be an array', 55 | 'Automaton final_states must be an array', 56 | 'Automaton states must be an array of length > 0', 57 | 'Automaton alphabets must be an array of length > 0', 58 | 'Automaton final_states must be an array of length > 0', 59 | ]); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "@docusaurus/Head"; 2 | import Link from '@docusaurus/Link'; 3 | import useBaseUrl from '@docusaurus/useBaseUrl'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import clsx from 'clsx'; 7 | import React from 'react'; 8 | import HomepageFeatures from "../components/HomepageFeatures"; 9 | import styles from './index.module.css'; 10 | 11 | function Feature({ imageUrl, title, description }) { 12 | const imgUrl = useBaseUrl(imageUrl); 13 | return ( 14 |
15 | {title} 16 |

{title}

17 |

{description}

18 |
19 | ); 20 | } 21 | 22 | function Home() { 23 | const context = useDocusaurusContext(); 24 | const { siteConfig } = context; 25 | return ( 26 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 |

{siteConfig.tagline}

37 |
38 | 44 | Get Started 45 | 46 |
47 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | ); 57 | } 58 | 59 | export default Home; 60 | -------------------------------------------------------------------------------- /packages/fa/libs/types.ts: -------------------------------------------------------------------------------- 1 | export interface InputFiniteAutomaton { 2 | // Append a string to all the states 3 | append?: string; 4 | alphabets: (string | number)[]; 5 | label: string; 6 | description?: string; 7 | start_state: string | number; 8 | final_states: (string | number)[]; 9 | states: (string | number)[]; 10 | // each key of transitions indicate a state 11 | transitions: Record< 12 | string | number, 13 | (Array | (string | number) | null)[] | 'loop' 14 | >; 15 | epsilon_transitions?: Record; 16 | } 17 | 18 | export interface TransformedFiniteAutomaton { 19 | append?: string; 20 | alphabets: string[]; 21 | label: string; 22 | description?: string; 23 | start_state: string; 24 | final_states: string[]; 25 | states: string[]; 26 | // each key of transitions indicate a state, which in turn represents alphabets 27 | transitions: Record>; 28 | epsilon_transitions: null | Record; 29 | } 30 | 31 | // eslint-disable-next-line 32 | export type IAutomatonTestLogicFn = (inputString: string, automatonTestResult: boolean) => boolean; 33 | // eslint-disable-next-line 34 | export type IAutomatonTestFn = (inputString: string) => boolean; 35 | export interface IFiniteAutomaton { 36 | testLogic: IAutomatonTestLogicFn; 37 | automaton: TransformedFiniteAutomaton; 38 | automatonId: string; 39 | } 40 | 41 | export interface IAutomatonInfo { 42 | test: IAutomatonTestFn; 43 | testLogic: IAutomatonTestLogicFn; 44 | automaton: { 45 | label: string; 46 | alphabets: string[]; 47 | description?: string; 48 | }; 49 | } 50 | 51 | export type TFiniteAutomatonType = 'deterministic' | 'non-deterministic' | 'epsilon'; 52 | export interface GraphNode { 53 | name: string; 54 | state: string; 55 | symbol: null | string; 56 | children: GraphNode[]; 57 | depth: number; 58 | string: string; 59 | } 60 | 61 | export type GeneratedAutomatonOptions = Partial< 62 | Pick['automaton'], 'label' | 'description'> & { 63 | separator: string; 64 | } 65 | >; 66 | export type TMergeOperation = 'or' | 'and' | 'not'; 67 | 68 | export interface SkipOptions { 69 | skipValidation: boolean; 70 | skipNormalization: boolean; 71 | skipCharacterRangesExpansion: boolean; 72 | } 73 | -------------------------------------------------------------------------------- /packages/cfg/libs/generateLR0ParsingTable.ts: -------------------------------------------------------------------------------- 1 | import { IContextFreeGrammar, IContextFreeGrammarInput } from "./types"; 2 | import { populateCfg } from "./utils/populateCfg"; 3 | 4 | const dotSymbol = "$.$" 5 | 6 | export function addDotToProductionRule (productionRules: IContextFreeGrammar["productionRules"], productionVariable: string) { 7 | const rulesForVariable = productionRules[productionVariable]; 8 | // Loop through all the rules for the variable 9 | rulesForVariable.forEach((rule, ruleIndex) => { 10 | // Add the dot symbol to the left 11 | rulesForVariable[ruleIndex] = `${dotSymbol} ${rule}` 12 | }); 13 | } 14 | 15 | export function generateClosureOfLR0Item(cfg: Omit, productionVariable: string) { 16 | const {productionRules, variables} = cfg; 17 | // Creating a set of variables for faster membership lookup 18 | const variablesSet = new Set(variables); 19 | 20 | const rules = productionRules[productionVariable]; 21 | rules.forEach(rule => { 22 | const tokens = rule.split(" "); 23 | for (let tokenNumber = 0; tokenNumber < tokens.length - 1; tokenNumber+=1) { 24 | const token = tokens[tokenNumber]; 25 | // We wont overflow the tokens array so its safe 26 | const nextToken = tokens[tokenNumber + 1]; 27 | // Using $.$ as its a lot less common than regular . 28 | // Check if the next token is a variable 29 | if (token === dotSymbol && variablesSet.has(nextToken)) { 30 | // Add dotSymbol to the left of all the substitution for the variable 31 | addDotToProductionRule(productionRules, nextToken); 32 | // Generate closure of the next variable 33 | generateClosureOfLR0Item(cfg, nextToken) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | export function augmentCfg(inputCfg: IContextFreeGrammarInput) { 40 | const cfg = populateCfg(inputCfg); 41 | const { productionRules, startVariable, variables } = cfg; 42 | // Create the augmented grammar 43 | const newStartVariable = `${startVariable}'`; 44 | variables.unshift(newStartVariable); 45 | productionRules[newStartVariable] = [startVariable] 46 | cfg.startVariable = newStartVariable 47 | return cfg; 48 | } 49 | 50 | export function generateLR0ParsingTable(inputCfg: IContextFreeGrammarInput) { 51 | augmentCfg(inputCfg); 52 | } -------------------------------------------------------------------------------- /packages/fa/tests/NonDeterministicFiniteAutomaton/convertToDeterministicFiniteAutomaton.test.ts: -------------------------------------------------------------------------------- 1 | import { convertToDeterministicFiniteAutomaton } from '../../libs/NonDeterministicFiniteAutomaton/convertToDeterministicFiniteAutomaton'; 2 | 3 | describe('convertToDeterministicFiniteAutomaton', () => { 4 | it(`Converting regular nfa to dfa`, () => { 5 | const convertedDfa = convertToDeterministicFiniteAutomaton( 6 | { 7 | start_state: 'q0', 8 | alphabets: ['a', 'b'], 9 | final_states: ['q1'], 10 | label: 'sample nfa', 11 | states: ['q0', 'q1', 'q2'], 12 | transitions: { 13 | q0: { 14 | a: ['q2', 'q1'], 15 | }, 16 | q2: { 17 | a: ['q2', 'q1'], 18 | b: ['q2'], 19 | }, 20 | }, 21 | epsilon_transitions: null, 22 | }, 23 | { 24 | description: 'New Description', 25 | label: 'New label', 26 | separator: '-', 27 | } 28 | ); 29 | expect(convertedDfa).toStrictEqual({ 30 | epsilon_transitions: null, 31 | alphabets: ['a', 'b'], 32 | final_states: ['q1-q2'], 33 | start_state: 'q0', 34 | states: ['q0', 'q1-q2', 'Ø', 'q2'], 35 | transitions: { 36 | q0: [['q1-q2'], ['Ø']], 37 | 'q1-q2': [['q1-q2'], ['q2']], 38 | q2: [['q1-q2'], ['q2']], 39 | Ø: ['Ø', 'Ø'], 40 | }, 41 | description: 'New Description', 42 | label: 'New label', 43 | }); 44 | }); 45 | 46 | it(`Converting regular e-nfa to dfa`, () => { 47 | const convertedDfa = convertToDeterministicFiniteAutomaton({ 48 | start_state: 'q0', 49 | alphabets: ['a', 'b'], 50 | final_states: ['q1'], 51 | label: 'sample nfa', 52 | states: ['q0', 'q1', 'q2'], 53 | transitions: { 54 | q0: { 55 | a: ['q2', 'q1'], 56 | }, 57 | q2: { 58 | a: ['q2', 'q1'], 59 | b: ['q2'], 60 | }, 61 | }, 62 | epsilon_transitions: { 63 | q0: ['q1'], 64 | }, 65 | }); 66 | expect(convertedDfa).toStrictEqual({ 67 | description: undefined, 68 | epsilon_transitions: null, 69 | alphabets: ['a', 'b'], 70 | final_states: ['q0,q1', 'q1,q2'], 71 | label: 'sample nfa', 72 | start_state: 'q0,q1', 73 | states: ['q0,q1', 'q1,q2', 'Ø', 'q2'], 74 | transitions: { 75 | 'q0,q1': [['q1,q2'], ['Ø']], 76 | 'q1,q2': [['q1,q2'], ['q2']], 77 | q2: [['q1,q2'], ['q2']], 78 | Ø: ['Ø', 'Ø'], 79 | }, 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/cfg/libs/removeUnreachableProduction.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from '@datastructures-js/linked-list'; 2 | import { IContextFreeGrammarInput } from './types'; 3 | import { populateCfg } from './utils/populateCfg'; 4 | import { setDifference } from './utils/setOperations'; 5 | 6 | /** 7 | * Removes unreachable variables and production of a cfg 8 | * @param cfg Production rules, start variable and variables array of cfg 9 | * @returns A new production rule record and variables with unreachable variable and rules removed 10 | */ 11 | export function removeUnreachableProduction( 12 | inputCfg: Pick 13 | ) { 14 | const cfg = populateCfg(inputCfg); 15 | const { productionRules, startVariable, variables } = cfg; 16 | 17 | const variablesSet = new Set(variables); 18 | const unvisitedVariables = new LinkedList(); 19 | unvisitedVariables.insertFirst(startVariable); 20 | const visitedVariables: Set = new Set(); 21 | visitedVariables.add(startVariable); 22 | 23 | // While we have unvisited variables 24 | while (unvisitedVariables.count()) { 25 | const unvisitedVariable = unvisitedVariables.removeFirst(); 26 | // For each of the unvisited variable, check which variables we can reach 27 | productionRules[unvisitedVariable.getValue()]?.forEach((productionRuleSubstitution) => { 28 | const tokens = productionRuleSubstitution.split(' '); 29 | for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) { 30 | const token = tokens[tokenIndex]; 31 | // If the letter is a variable, and we haven't visited the variable yet 32 | if (variablesSet.has(token) && !visitedVariables.has(token)) { 33 | visitedVariables.add(token); 34 | unvisitedVariables.insertLast(token); 35 | } 36 | } 37 | }); 38 | } 39 | // The difference between the variables set and visited variables set will return the variables which couldn't be reached 40 | const nonReachableVariables = Array.from(setDifference(variablesSet, visitedVariables)); 41 | 42 | // Delete the production rules for each of the unreachable variables 43 | nonReachableVariables.forEach((nonReachableVariable) => { 44 | delete productionRules[nonReachableVariable]; 45 | }); 46 | // There is no need to update the production rules as unreachable variables are not referenced there 47 | return Array.from(visitedVariables); 48 | } 49 | -------------------------------------------------------------------------------- /packages/testing/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fauton/testing", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "5.0.1", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 10 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 11 | }, 12 | "cli-progress": { 13 | "version": "3.9.1", 14 | "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.9.1.tgz", 15 | "integrity": "sha512-AXxiCe2a0Lm0VN+9L0jzmfQSkcZm5EYspfqXKaSIQKqIk+0hnkZ3/v1E9B39mkD6vYhKih3c/RPsJBSwq9O99Q==", 16 | "requires": { 17 | "colors": "^1.1.2", 18 | "string-width": "^4.2.0" 19 | } 20 | }, 21 | "colors": { 22 | "version": "1.4.0", 23 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 24 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 25 | }, 26 | "emoji-regex": { 27 | "version": "8.0.0", 28 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 29 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 30 | }, 31 | "is-fullwidth-code-point": { 32 | "version": "3.0.0", 33 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 34 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 35 | }, 36 | "string-width": { 37 | "version": "4.2.3", 38 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 39 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 40 | "requires": { 41 | "emoji-regex": "^8.0.0", 42 | "is-fullwidth-code-point": "^3.0.0", 43 | "strip-ansi": "^6.0.1" 44 | } 45 | }, 46 | "strip-ansi": { 47 | "version": "6.0.1", 48 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 49 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 50 | "requires": { 51 | "ansi-regex": "^5.0.1" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/cfg/libs/generateLL1ParsingTable.ts: -------------------------------------------------------------------------------- 1 | import { findFollow } from "./findFollow"; 2 | import { IContextFreeGrammarInput } from "./types"; 3 | import { populateCfg } from "./utils/populateCfg"; 4 | 5 | /** 6 | * Generate LL1 Parsing table 7 | * @param inputCfg Input context free grammar 8 | * @returns A record for LL1 Parsed table and whether the grammar is LL1 parsable 9 | */ 10 | export function generateLL1ParsingTable(inputCfg: IContextFreeGrammarInput) { 11 | const cfg = populateCfg(inputCfg); 12 | // Find the first and follow record of cfg 13 | const followRecord = findFollow(cfg); 14 | let isParsable = true; 15 | const {variables} = cfg; 16 | 17 | // First key for variable, 2nd key for terminal and value is rule number or null 18 | const llRecord: Record> = {}; 19 | 20 | // Populating the table 21 | // All variables as the row 22 | // All terminals as the column 23 | variables.forEach(variable => { 24 | llRecord[variable] = {}; 25 | }) 26 | 27 | // Loop through all the first record entries 28 | Object.entries(followRecord.first).forEach(([productionVariable, firstRecord]) => { 29 | // Substitutions is an array which contains first(substitution) 30 | firstRecord.substitutions.forEach((firstTokensForSubstitution, ruleNumber) => { 31 | // Loop through all the first token for the substitution 32 | firstTokensForSubstitution.forEach(firstTokenForSubstitution => { 33 | // If its epsilon we need to get the followed tokens of this variable 34 | if (firstTokenForSubstitution === "") { 35 | // Loop through each followed token and assign the rule number 36 | followRecord.follow[productionVariable].forEach((followedToken) => { 37 | if (followedToken in llRecord[productionVariable]) { 38 | isParsable = false; 39 | } 40 | llRecord[productionVariable][followedToken] = ruleNumber 41 | }) 42 | } else { 43 | // TODO: No tests reaches this path, maybe its not required? 44 | // if (firstTokenForSubstitution in llRecord[productionVariable]) { 45 | // isParsable = false; 46 | // } 47 | llRecord[productionVariable][firstTokenForSubstitution] = ruleNumber 48 | } 49 | }) 50 | }) 51 | }) 52 | 53 | return { 54 | parseTable: llRecord, 55 | isParsable 56 | }; 57 | } -------------------------------------------------------------------------------- /packages/testing/libs/utils/createFileWriteStreams.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { IOutputFiles } from '../types'; 4 | 5 | type IWriteStreams = Record<`${keyof IOutputFiles}WriteStream`, null | fs.WriteStream>; 6 | 7 | /** 8 | * Create file write streams for logging various info regarding the automaton test 9 | * @param logsPath Path to the log directory 10 | * @param automatonLabel Label of the automaton 11 | * @param outputFiles A record that represents which files to generate 12 | * @returns A record of write streams and a function to end all streams 13 | */ 14 | export function createFileWriteStreams( 15 | logsPath: string, 16 | automatonLabel: string, 17 | outputFiles?: Partial 18 | ) { 19 | // Create the write stream record 20 | // Each key would represent which sort of file it is and the value would be the write stream itself 21 | // Not all write stream need to be created, so only create those that are necessary 22 | const writeStreamsRecord: IWriteStreams = { 23 | caseWriteStream: outputFiles?.case 24 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.case.txt`)) 25 | : null, 26 | aggregateWriteStream: outputFiles?.aggregate 27 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.aggregate.txt`)) 28 | : null, 29 | incorrectWriteStream: outputFiles?.incorrect 30 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.incorrect.txt`)) 31 | : null, 32 | correctWriteStream: outputFiles?.correct 33 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.correct.txt`)) 34 | : null, 35 | inputWriteStream: outputFiles?.input 36 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.input.txt`)) 37 | : null, 38 | acceptedWriteStream: outputFiles?.accepted 39 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.accepted.txt`)) 40 | : null, 41 | rejectedWriteStream: outputFiles?.rejected 42 | ? fs.createWriteStream(path.resolve(logsPath, `${automatonLabel}.rejected.txt`)) 43 | : null, 44 | }; 45 | return { 46 | record: writeStreamsRecord, 47 | // Loop through all the streams and end them 48 | endStreams() { 49 | Object.values(writeStreamsRecord).forEach((writeStream) => { 50 | // A write stream might not be defined 51 | if (writeStream) { 52 | writeStream.end(); 53 | } 54 | }); 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /docs/src/theme/Toggle/styles.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | .toggle { 9 | touch-action: pan-x; 10 | position: relative; 11 | cursor: pointer; 12 | user-select: none; 13 | -webkit-tap-highlight-color: transparent; 14 | } 15 | 16 | .toggleScreenReader { 17 | border: 0; 18 | clip: rect(0 0 0 0); 19 | height: 1px; 20 | margin: -1px; 21 | overflow: hidden; 22 | position: absolute; 23 | width: 1px; 24 | } 25 | 26 | .toggleDisabled { 27 | cursor: not-allowed; 28 | } 29 | 30 | .toggleTrack { 31 | width: 50px; 32 | height: 24px; 33 | border-radius: 30px; 34 | background-color: #4d4d4d; 35 | transition: all 0.2s ease; 36 | } 37 | 38 | .toggleTrackCheck { 39 | position: absolute; 40 | width: 14px; 41 | height: 10px; 42 | top: 0px; 43 | bottom: 0px; 44 | margin: auto 0; 45 | left: 8px; 46 | opacity: 0; 47 | transition: opacity 0.25s ease; 48 | } 49 | 50 | [data-theme='dark'] .toggle .toggleTrackCheck, 51 | .toggleChecked .toggleTrackCheck { 52 | opacity: 1; 53 | transition: opacity 0.25s ease; 54 | } 55 | 56 | .toggleTrackX { 57 | position: absolute; 58 | width: 10px; 59 | height: 10px; 60 | top: 0px; 61 | bottom: 0px; 62 | margin: auto 0; 63 | right: 10px; 64 | opacity: 1; 65 | transition: opacity 0.25s ease; 66 | } 67 | 68 | [data-theme='dark'] .toggle .toggleTrackX, 69 | .toggleChecked .toggleTrackX { 70 | opacity: 0; 71 | } 72 | 73 | .toggleTrackThumb { 74 | position: absolute; 75 | top: 1px; 76 | left: 1px; 77 | width: 22px; 78 | height: 22px; 79 | border: 1px solid #4d4d4d; 80 | border-radius: 50%; 81 | background-color: #fafafa; 82 | transition: all 0.25s ease; 83 | } 84 | 85 | [data-theme='dark'] .toggle .toggleTrackThumb, 86 | .toggleChecked .toggleTrackThumb { 87 | left: 27px; 88 | } 89 | 90 | .toggleFocused .toggleTrackThumb, 91 | .toggle:hover .toggleTrackThumb { 92 | box-shadow: 0px 0px 2px 3px var(--ifm-color-primary); 93 | } 94 | 95 | .toggle:active:not(.toggleDisabled) .toggleTrackThumb { 96 | box-shadow: 0px 0px 5px 5px var(--ifm-color-primary); 97 | } 98 | 99 | .toggleIcon { 100 | align-items: center; 101 | display: flex; 102 | height: 10px; 103 | justify-content: center; 104 | width: 10px; 105 | } 106 | -------------------------------------------------------------------------------- /packages/cfg/tests/removeNullProduction.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createProductionCombinations, 3 | findNullableVariables, 4 | removeNullProduction, 5 | } from '../libs/removeNullProduction'; 6 | import { arrayEquivalency } from './setEquivalency'; 7 | 8 | describe('removeNullProduction', () => { 9 | it(`Known variables present in production rules`, () => { 10 | const productionRules = { 11 | Sub: ['Adj Verb Adj Conj'], 12 | Adj: ['a Adj', ''], 13 | Verb: ['b Verb', ''], 14 | Conj: ['c'], 15 | }; 16 | removeNullProduction({ 17 | productionRules, 18 | variables: ['Sub', 'Adj', 'Verb', 'Conj'], 19 | startVariable: 'Sub', 20 | }); 21 | 22 | expect(productionRules).toStrictEqual({ 23 | Sub: [ 24 | 'Conj', 25 | 'Verb Conj', 26 | 'Adj Conj', 27 | 'Adj Verb Conj', 28 | 'Verb Adj Conj', 29 | 'Adj Adj Conj', 30 | 'Adj Verb Adj Conj', 31 | ], 32 | Adj: ['a', 'a Adj'], 33 | Verb: ['b', 'b Verb'], 34 | Conj: ['c'], 35 | }); 36 | }); 37 | 38 | it(`Unknown variable in production rules`, () => { 39 | const productionRules = { 40 | Sub: ['Adj', 'Verb', 'Conj'], 41 | Adj: ['an Adj for', '', 'Verb'], 42 | Verb: ['be Verb early', '', 'Conj'], 43 | Conj: ['can Conj do', ''], 44 | }; 45 | removeNullProduction({ 46 | startVariable: 'Sub', 47 | productionRules, 48 | // Note that Unknown is not present in productionRules 49 | variables: ['Sub', 'Adj', 'Verb', 'Conj', 'Unknown'], 50 | }); 51 | 52 | expect(productionRules).toStrictEqual({ 53 | Sub: ['Adj', 'Verb', 'Conj'], 54 | Adj: ['an for', 'an Adj for', 'Verb'], 55 | Verb: ['be early', 'be Verb early', 'Conj'], 56 | Conj: ['can do', 'can Conj do'], 57 | }); 58 | }); 59 | }); 60 | 61 | it(`Should create all combinations of production rules`, () => { 62 | expect(createProductionCombinations('Adj be Adj', 'Adj', 'be')).toStrictEqual([ 63 | ['Adj be', 'be Adj', 'Adj be Adj'], 64 | false, 65 | 2, 66 | ]); 67 | }); 68 | 69 | it(`Should find all nullable variables`, () => { 70 | const productionRules = { 71 | Sub: ['Adj Verb', 'an be can'], 72 | Adj: [''], 73 | Verb: ['Adj', 'be'], 74 | Conj: ['can'], 75 | Det: ['Adj Verb', 'can'], 76 | }; 77 | expect( 78 | arrayEquivalency( 79 | findNullableVariables({ 80 | productionRules, 81 | variables: ['Sub', 'Adj', 'Verb', 'Conj', 'Det', 'Noun'], 82 | }), 83 | ['Adj', 'Verb', 'Det', 'Sub'] 84 | ) 85 | ).toBe(true); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/fa/libs/NonDeterministicFiniteAutomaton/index.ts: -------------------------------------------------------------------------------- 1 | import { DeterministicFiniteAutomaton } from '../DeterministicFiniteAutomaton'; 2 | import { FiniteAutomaton } from '../FiniteAutomaton'; 3 | import { 4 | GeneratedAutomatonOptions, 5 | IAutomatonTestLogicFn, 6 | InputFiniteAutomaton, 7 | SkipOptions, 8 | } from '../types'; 9 | import { convertToDeterministicFiniteAutomaton } from './convertToDeterministicFiniteAutomaton'; 10 | import { convertToRegularNfa } from './convertToRegularNfa'; 11 | import { epsilonClosureOfState } from './epsilonClosureOfState'; 12 | import { moveAndEpsilonClosureStateSet } from './moveAndEpsilonClosureStateSet'; 13 | 14 | export { 15 | convertToDeterministicFiniteAutomaton, 16 | convertToRegularNfa, 17 | epsilonClosureOfState, 18 | moveAndEpsilonClosureStateSet, 19 | }; 20 | 21 | export class NonDeterministicFiniteAutomaton extends FiniteAutomaton { 22 | constructor( 23 | testLogic: IAutomatonTestLogicFn, 24 | automaton: InputFiniteAutomaton, 25 | automatonId?: string, 26 | skipOptions?: Partial 27 | ) { 28 | super( 29 | testLogic, 30 | automaton, 31 | automaton.epsilon_transitions ? 'epsilon' : 'non-deterministic', 32 | automatonId, 33 | skipOptions 34 | ); 35 | if (this.automaton.epsilon_transitions) { 36 | this.convertToRegularNfa(); 37 | } 38 | } 39 | 40 | convertToRegularNfa() { 41 | convertToRegularNfa(this.automaton); 42 | } 43 | 44 | /** 45 | * Generates a set of states reachable from input state, using depth-first-search algorithm 46 | * @param state state from where to start searching for epsilon closure states 47 | * @returns A set of states reachable from the input state on all epsilon transitions 48 | */ 49 | epsilonClosureOfState(state: string) { 50 | return epsilonClosureOfState(this.automaton.epsilon_transitions, state); 51 | } 52 | 53 | // ε-closure(Move_NFA(states, letter)) 54 | moveAndEpsilonClosureStateSet(states: string[], symbol: string) { 55 | return moveAndEpsilonClosureStateSet( 56 | this.automaton.transitions, 57 | this.automaton.epsilon_transitions, 58 | states, 59 | symbol 60 | ); 61 | } 62 | 63 | convertToDeterministicFiniteAutomaton(dfaOptions?: GeneratedAutomatonOptions) { 64 | return new DeterministicFiniteAutomaton( 65 | this.testLogic, 66 | convertToDeterministicFiniteAutomaton(this.automaton, dfaOptions), 67 | undefined, 68 | { 69 | skipCharacterRangesExpansion: true, 70 | } 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/fa/libs/FiniteAutomaton/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign, no-console */ 2 | import shortid from 'shortid'; 3 | import { 4 | IAutomatonTestLogicFn, 5 | InputFiniteAutomaton, 6 | SkipOptions, 7 | TFiniteAutomatonType, 8 | TransformedFiniteAutomaton, 9 | } from '../types'; 10 | import { generateParseTreeForString } from './generateParseTreeForString'; 11 | import { generatePostNormalizationErrors } from './generatePostNormalizationErrors'; 12 | import { generatePreNormalizationErrors } from './generatePreNormalizationErrors'; 13 | import { normalize } from './normalize'; 14 | import { validate } from './validate'; 15 | 16 | export { 17 | generateParseTreeForString, 18 | generatePostNormalizationErrors, 19 | generatePreNormalizationErrors, 20 | normalize, 21 | validate, 22 | }; 23 | export class FiniteAutomaton { 24 | testLogic: IAutomatonTestLogicFn; 25 | 26 | automaton: TransformedFiniteAutomaton; 27 | 28 | #automatonId: string; 29 | 30 | #automatonType: TFiniteAutomatonType; 31 | 32 | constructor( 33 | testLogic: IAutomatonTestLogicFn, 34 | finiteAutomaton: InputFiniteAutomaton | TransformedFiniteAutomaton, 35 | automatonType: TFiniteAutomatonType, 36 | automatonId?: string, 37 | skipOptions?: Partial 38 | ) { 39 | this.#automatonType = automatonType; 40 | this.#automatonId = automatonId ?? shortid(); 41 | this.testLogic = testLogic; 42 | // Validate the automaton passed before normalizing it 43 | if (!skipOptions?.skipValidation) { 44 | validate( 45 | finiteAutomaton.label, 46 | generatePreNormalizationErrors(testLogic, this.#automatonType, finiteAutomaton) 47 | ); 48 | } 49 | if (!skipOptions?.skipNormalization) { 50 | this.automaton = normalize( 51 | finiteAutomaton, 52 | skipOptions?.skipCharacterRangesExpansion ?? false 53 | ); 54 | } else { 55 | this.automaton = finiteAutomaton as any; 56 | } 57 | 58 | if (!skipOptions?.skipValidation) { 59 | // Validate the automaton passed after normalizing it 60 | validate( 61 | finiteAutomaton.label, 62 | generatePostNormalizationErrors(this.automaton as TransformedFiniteAutomaton) 63 | ); 64 | } 65 | } 66 | 67 | getAutomatonId() { 68 | return this.#automatonId; 69 | } 70 | 71 | generateParseTreeForString(inputString: string) { 72 | return generateParseTreeForString(this.automaton, inputString); 73 | } 74 | 75 | test(inputString: string) { 76 | return this.generateParseTreeForString(inputString).verdict; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/regex/libs/utils/generateRegexTree.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | 3 | import { BinaryRegexNode, BinaryRegexOperators, BinaryRegexOperatorsName, RegexNode, UnaryRegexNode, UnaryRegexOperators, UnaryRegexOperatorsName } from '../types'; 4 | 5 | const RegexOperatorKeywordRecord: Record = { 6 | ".": "Concat", 7 | "|": "Or", 8 | "*": "Kleene", 9 | "+": "Plus", 10 | "?": "Optional" 11 | }; 12 | 13 | // Set of binary regex operators 14 | const binaryRegexOperators = new Set('.|'); 15 | // Set of unary regex operators 16 | const unaryRegexOperators = new Set('*+?'); 17 | 18 | export function generateRegexTreeBinaryOperatorNode(regexString: string): [RegexNode, string] { 19 | const [rightNode, regex1] = generateRegexTree(regexString.slice(0, regexString.length - 1)); 20 | const [leftNode, regex2] = generateRegexTree(regex1); 21 | 22 | return [ 23 | { 24 | operator: RegexOperatorKeywordRecord[regexString[regexString.length - 1] as BinaryRegexOperators], 25 | operands: [leftNode, rightNode], 26 | } as BinaryRegexNode, 27 | regex2, 28 | ]; 29 | } 30 | 31 | export function generateRegexTreeUnaryOperatorNode(regexString: string): [RegexNode, string] { 32 | const [childNodes, regex] = generateRegexTree(regexString.slice(0, regexString.length - 1)); 33 | return [ 34 | { 35 | operator: RegexOperatorKeywordRecord[regexString[regexString.length - 1] as UnaryRegexOperators], 36 | operands: [childNodes], 37 | } as UnaryRegexNode, 38 | regex, 39 | ]; 40 | } 41 | 42 | export function generateRegexTreeLiteralNode(regexString: string): [RegexNode, string] { 43 | return [ 44 | { 45 | operator: 'Literal', 46 | operands: [regexString[regexString.length - 1]], 47 | }, 48 | regexString.slice(0, regexString.length - 1), 49 | ]; 50 | } 51 | 52 | export function generateRegexTree(postfixRegexString: string) { 53 | const lastSymbolInPostfixRegexString = postfixRegexString[postfixRegexString.length - 1]; 54 | // Checking for binary operators 55 | if (binaryRegexOperators.has(lastSymbolInPostfixRegexString)) { 56 | return generateRegexTreeBinaryOperatorNode(postfixRegexString); 57 | } 58 | // Checking for unary operators 59 | else if (unaryRegexOperators.has(lastSymbolInPostfixRegexString)) { 60 | return generateRegexTreeUnaryOperatorNode(postfixRegexString); 61 | } 62 | // Checking for regular literals 63 | return generateRegexTreeLiteralNode(postfixRegexString); 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | build 121 | old 122 | *.zip 123 | 124 | eslint.txt 125 | experiment 126 | dfa 127 | todo.md 128 | 129 | .vscode -------------------------------------------------------------------------------- /packages/fa/libs/FiniteAutomaton/generateParseTreeForString.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, TransformedFiniteAutomaton } from '../types'; 2 | 3 | /** 4 | * Generate parse tree for a given automaton and input string 5 | * @param automaton Automaton to generate parse tree from 6 | * @param inputString Input string to parse 7 | * @returns A parse tree, verdict on whether the string is accepted and the leaf nodes 8 | */ 9 | export function generateParseTreeForString( 10 | automaton: Pick, 11 | // TODO: This should be an array of tokens 12 | inputString: string 13 | ) { 14 | let currentParents: GraphNode[] = [ 15 | { 16 | name: `${automaton.start_state}`, 17 | state: automaton.start_state, 18 | string: '', 19 | depth: 0, 20 | symbol: null, 21 | children: [], 22 | }, 23 | ]; 24 | let verdict = false; 25 | const finalStates = new Set(automaton.final_states); 26 | 27 | const tree = currentParents; 28 | for (let index = 0; index < inputString.length; index += 1) { 29 | const newParents: GraphNode[] = []; 30 | const symbol = inputString[index]; 31 | currentParents.forEach((currentParent) => { 32 | const transitionStateRecord = automaton.transitions[currentParent.state]; 33 | if (transitionStateRecord) { 34 | const transitionTargetStates = transitionStateRecord[symbol]; 35 | // Guarding against null values 36 | if (Array.isArray(transitionTargetStates)) { 37 | transitionTargetStates.forEach((transitionTargetState) => { 38 | const parentGraphNode = { 39 | name: `${transitionTargetState}(${symbol})`, 40 | state: transitionTargetState, 41 | string: inputString.slice(0, index + 1), 42 | depth: index + 1, 43 | symbol, 44 | children: [], 45 | }; 46 | currentParent.children.push(parentGraphNode); 47 | newParents.push(parentGraphNode); 48 | }); 49 | } 50 | } 51 | }); 52 | // for the Last symbol 53 | if (index === inputString.length - 1) { 54 | // Looping through each of the new parent nodes 55 | // to see if any of their state matches the final state 56 | for (let newParentsIndex = 0; newParentsIndex < newParents.length; newParentsIndex += 1) { 57 | const newChild = newParents[newParentsIndex]; 58 | if (finalStates.has(newChild.state)) { 59 | verdict = true; 60 | break; 61 | } 62 | } 63 | } 64 | currentParents = newParents; 65 | } 66 | return { 67 | verdict, 68 | leafNodes: currentParents, 69 | tree: tree[0], 70 | }; 71 | } 72 | --------------------------------------------------------------------------------