├── .commitlintrc.json ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── run_test.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.cjs ├── .release-it.json ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── .vitepress │ ├── cache │ │ └── deps │ │ │ ├── @theme_index.js │ │ │ ├── @theme_index.js.map │ │ │ ├── _metadata.json │ │ │ ├── package.json │ │ │ ├── vue.js │ │ │ └── vue.js.map │ └── config.ts ├── api-examples.md ├── basics │ ├── do-while.md │ ├── for.md │ ├── function.md │ ├── if.md │ ├── operators.md │ ├── switch-case.md │ └── variables.md ├── getting-started.md ├── index.md └── introduction.md ├── nx.json ├── package.json ├── packages ├── parser │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.js │ ├── src │ │ ├── __test__ │ │ │ ├── identifier-match-keyword.test.ts │ │ │ └── program.test.ts │ │ ├── constants │ │ │ └── specs.ts │ │ ├── index.ts │ │ ├── nodes │ │ │ ├── Program.ts │ │ │ ├── declarations │ │ │ │ ├── ClassDeclaration.ts │ │ │ │ ├── Declaration.ts │ │ │ │ ├── FunctionDeclaration.ts │ │ │ │ ├── ParameterList.ts │ │ │ │ ├── VariableDeclaration.ts │ │ │ │ ├── VariableDeclarator.ts │ │ │ │ ├── __test__ │ │ │ │ │ ├── declaration-class.test.ts │ │ │ │ │ ├── declaration-export.test.ts │ │ │ │ │ ├── declaration-import.test.ts │ │ │ │ │ ├── declaration-variable.test.ts │ │ │ │ │ └── generator │ │ │ │ │ │ ├── generator-declaration-class.test.ts │ │ │ │ │ │ └── generator-declaration-variable.test.ts │ │ │ │ ├── class │ │ │ │ │ ├── ClassBody.ts │ │ │ │ │ ├── ClassElementList.ts │ │ │ │ │ ├── ClassMethod.ts │ │ │ │ │ └── ClassProperty.ts │ │ │ │ ├── export │ │ │ │ │ ├── ExportAllDeclaration.ts │ │ │ │ │ ├── ExportDeclaration.ts │ │ │ │ │ ├── ExportDefaultDeclaration.ts │ │ │ │ │ ├── ExportNamedDeclaration.ts │ │ │ │ │ ├── ExportSpecifier.ts │ │ │ │ │ └── ExportsList.ts │ │ │ │ └── import │ │ │ │ │ ├── ImportClause.ts │ │ │ │ │ ├── ImportDeclaration.ts │ │ │ │ │ ├── ImportDefaultSpecifier.ts │ │ │ │ │ ├── ImportNamespaceSpecifier.ts │ │ │ │ │ ├── ImportSpecifier.ts │ │ │ │ │ └── ImportsList.ts │ │ │ ├── expressions │ │ │ │ ├── ArrayExpression.ts │ │ │ │ ├── AssignmentExpression.ts │ │ │ │ ├── AwaitExpression.ts │ │ │ │ ├── BinaryExpression.ts │ │ │ │ ├── CallExpression.ts │ │ │ │ ├── Expression.ts │ │ │ │ ├── FunctionExpression.ts │ │ │ │ ├── LogicalExpression.ts │ │ │ │ ├── MemberExpression.ts │ │ │ │ ├── ObjectExpression.ts │ │ │ │ ├── PrimaryExpression.ts │ │ │ │ ├── ThisExpression.ts │ │ │ │ ├── UnaryExpression.ts │ │ │ │ ├── UpdateExpression.ts │ │ │ │ ├── YieldExpression.ts │ │ │ │ └── __test__ │ │ │ │ │ ├── expression-array.test.ts │ │ │ │ │ ├── expression-binary.test.ts │ │ │ │ │ ├── expression-call.test.ts │ │ │ │ │ ├── expression-member.test.ts │ │ │ │ │ ├── expression-object.test.ts │ │ │ │ │ └── generator │ │ │ │ │ ├── generator-expression-array.test.ts │ │ │ │ │ ├── generator-expression-binary.test.ts │ │ │ │ │ ├── generator-expression-call.test.ts │ │ │ │ │ ├── generator-expression-member.test.ts │ │ │ │ │ └── generator-expression-object.test.ts │ │ │ ├── identifier │ │ │ │ ├── Identifier.ts │ │ │ │ └── __test__ │ │ │ │ │ └── identifier.test.ts │ │ │ ├── initializers │ │ │ │ ├── array │ │ │ │ │ └── ArrayElementList.ts │ │ │ │ └── object │ │ │ │ │ ├── ObjectMethod.ts │ │ │ │ │ ├── ObjectProperty.ts │ │ │ │ │ └── ObjectPropertyList.ts │ │ │ ├── literals │ │ │ │ ├── ArrayLiteral.ts │ │ │ │ ├── BooleanLiteral.ts │ │ │ │ ├── Literal.ts │ │ │ │ ├── NaNIdentifier.ts │ │ │ │ ├── NullLiteral.ts │ │ │ │ ├── NumericLiteral.ts │ │ │ │ ├── ObjectLiteral.ts │ │ │ │ ├── StringLiteral.ts │ │ │ │ ├── UndefinedIdentifier.ts │ │ │ │ └── __test__ │ │ │ │ │ ├── generator │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ ├── generator-literal-boolean.test.ts.snap │ │ │ │ │ │ ├── generator-literal-numeric.test.ts.snap │ │ │ │ │ │ └── generator-literal-string.test.ts.snap │ │ │ │ │ ├── generator-literal-boolean.test.ts │ │ │ │ │ ├── generator-literal-numeric.test.ts │ │ │ │ │ └── generator-literal-string.test.ts │ │ │ │ │ ├── literal-boolean.test.ts │ │ │ │ │ ├── literal-numeric.test.ts │ │ │ │ │ └── literal-string.test.ts │ │ │ └── statements │ │ │ │ ├── BlockStatement.ts │ │ │ │ ├── BreakStatement.ts │ │ │ │ ├── ContinueStatement.ts │ │ │ │ ├── DebuggerStatement.ts │ │ │ │ ├── EmptyStatement.ts │ │ │ │ ├── ExpressionStatement.ts │ │ │ │ ├── IfStatement.ts │ │ │ │ ├── LabelledStatement.ts │ │ │ │ ├── ReturnStatement.ts │ │ │ │ ├── Statement.ts │ │ │ │ ├── StatementList.ts │ │ │ │ ├── StatementListItem.ts │ │ │ │ ├── ThrowStatement.ts │ │ │ │ ├── WithStatement.ts │ │ │ │ ├── __test__ │ │ │ │ └── statement-if.test.ts │ │ │ │ ├── breakable │ │ │ │ ├── BreakableStatement.ts │ │ │ │ ├── SwitchStatement.ts │ │ │ │ ├── __test__ │ │ │ │ │ └── statement-breakable-switch.test.ts │ │ │ │ └── iteration │ │ │ │ │ ├── DoWhileStatement.ts │ │ │ │ │ ├── ForInOfStatement.ts │ │ │ │ │ ├── ForStatement.ts │ │ │ │ │ ├── IterationStatement.ts │ │ │ │ │ └── WhileStatement.ts │ │ │ │ └── try │ │ │ │ ├── CatchClause.ts │ │ │ │ ├── TryStatement.ts │ │ │ │ └── __test__ │ │ │ │ └── statement-try.test.ts │ │ ├── parser.ts │ │ ├── setup-test.ts │ │ ├── toPlainObject.ts │ │ ├── tokenizer.ts │ │ └── utils │ │ │ └── is-expression.ts │ ├── tsconfig.json │ └── vite.config.ts ├── plugins │ ├── vite │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── webpack │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts └── shared │ ├── index.ts │ ├── package.json │ └── parser │ ├── keyword.enum.ts │ ├── node.interface.ts │ ├── operator.type.ts │ ├── spec.type.ts │ └── token.type.ts ├── playground ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── favicon.ico ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ └── vite.svg ├── src │ ├── App.tsx │ ├── components │ │ ├── Results.tsx │ │ ├── TestCases.tsx │ │ ├── TestResults.tsx │ │ └── layouts │ │ │ └── DefaultLayout.tsx │ ├── configs │ │ ├── antd.config.ts │ │ └── index.ts │ ├── constants │ │ └── index.ts │ ├── editor │ │ ├── autocomplete.ts │ │ ├── config.ts │ │ └── vietscript.ts │ ├── guard │ │ └── AdminGuard.tsx │ ├── index.css │ ├── interface │ │ └── app.ts │ ├── main.tsx │ ├── pages │ │ ├── Dev.tsx │ │ ├── Home.tsx │ │ └── NotFound.tsx │ ├── routes │ │ └── DefaultRoutes.tsx │ ├── types │ │ └── index.ts │ ├── useRoutesElement.tsx │ ├── utils │ │ ├── index.ts │ │ └── supabase.ts │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── sandbox ├── package.json ├── src │ └── index.vjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── tsconfig.json └── vitest.config.ts /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | 4 | *.md 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | }, 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:import/errors", 10 | "plugin:import/warnings", 11 | "plugin:import/typescript", 12 | "plugin:@typescript-eslint/recommended", 13 | "plugin:unicorn/recommended", 14 | ], 15 | parser: "@typescript-eslint/parser", 16 | parserOptions: { 17 | ecmaFeatures: { 18 | jsx: true, 19 | }, 20 | ecmaVersion: 12, 21 | sourceType: "module", 22 | }, 23 | plugins: ["@typescript-eslint", "unicorn"], 24 | rules: { 25 | "import/no-unresolved": "off", 26 | "import/prefer-default-export": "off", 27 | "import/order": [ 28 | "error", 29 | { 30 | "newlines-between": "always", 31 | groups: ["builtin", "external", "internal", "parent", "sibling", "index"], 32 | }, 33 | ], 34 | "arrow-body-style": ["error", "as-needed"], 35 | "no-shadow": "off", 36 | "no-unused-vars": "off", 37 | "lines-between-class-members": ["error", "always"], 38 | "no-param-reassign": ["warn", { props: true, ignorePropertyModificationsFor: ["draft"] }], 39 | "padding-line-between-statements": [ 40 | "error", 41 | { blankLine: "always", prev: "*", next: "return" }, 42 | { blankLine: "always", prev: ["const", "let", "var"], next: "*" }, 43 | { 44 | blankLine: "any", 45 | prev: ["const", "let", "var"], 46 | next: ["const", "let", "var"], 47 | }, 48 | ], 49 | // Typescript 50 | "@typescript-eslint/no-explicit-any": "off", 51 | "@typescript-eslint/ban-ts-comment": "off", 52 | "@typescript-eslint/no-unused-vars": [ 53 | "error", 54 | { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, 55 | ], 56 | "@typescript-eslint/no-shadow": ["error", { allow: ["_"] }], 57 | // Unicorn 58 | "unicorn/prevent-abbreviations": "off", 59 | "unicorn/no-nested-ternary": "off", 60 | "unicorn/filename-case": "off", 61 | "unicorn/no-null": "off", 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /.github/workflows/run_test.yml: -------------------------------------------------------------------------------- 1 | # Github Action to run pnpm test on every push and pull request 2 | name: Run Test 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | unit-test: 11 | strategy: 12 | matrix: 13 | node-version: [16.x, 18.x, 20.x] 14 | os: [ubuntu-latest, windows-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Setup Node.js with ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - uses: pnpm/action-setup@v2 25 | name: Install pnpm 26 | with: 27 | version: 8 28 | run_install: true 29 | 30 | - name: Get pnpm store directory 31 | shell: bash 32 | run: | 33 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 34 | 35 | - uses: actions/cache@v3 36 | name: Setup pnpm cache 37 | with: 38 | path: ${{ env.STORE_PATH }} 39 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 40 | restore-keys: | 41 | ${{ runner.os }}-pnpm-store- 42 | 43 | - name: Run unit tests 44 | run: pnpm test 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | 11 | dist 12 | node_modules 13 | 14 | .DS_Store 15 | .env -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | 4 | *.md 5 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | bracketSpacing: true, 4 | semi: true, 5 | singleQuote: false, 6 | tabWidth: 2, 7 | trailingComma: "all", 8 | endOfLine: "auto" 9 | } 10 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: update tags and release project to v${version}" 4 | }, 5 | "npm": { 6 | "publish": false 7 | }, 8 | "github": { 9 | "release": true 10 | }, 11 | "plugins": { 12 | "@release-it/conventional-changelog": { 13 | "preset": "conventionalcommits", 14 | "infile": "CHANGELOG.md", 15 | "types": [ 16 | {"type": "feat", "section": "Features"}, 17 | {"type": "fix", "section": "Fixes"}, 18 | {"type": "chore", "section": "Minor updates"}, 19 | {"type": "docs", "section": "Minor updates"}, 20 | {"type": "style", "section": "Minor updates"}, 21 | {"type": "refactor", "section": "Features"}, 22 | {"type": "perf", "section": "Minor updates"}, 23 | {"type": "test", "section": "Minor updates"} 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[html]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "vscode.typescript-language-features" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[jsonc]": { 12 | "editor.defaultFormatter": "vscode.json-language-features" 13 | }, 14 | "[typescript]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[vue]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | "editor.defaultFormatter": "esbenp.prettier-vscode", 21 | "editor.detectIndentation": false, 22 | "editor.formatOnSave": true, 23 | "editor.renderWhitespace": "all", 24 | "editor.rulers": [ 25 | 120 26 | ], 27 | "editor.tabSize": 2, 28 | "javascript.suggest.autoImports": true, 29 | "javascript.updateImportsOnFileMove.enabled": "always", 30 | "prettier.configPath": "./.prettierrc.cjs", 31 | "prettier.htmlWhitespaceSensitivity": "ignore", 32 | "prettier.printWidth": 120, 33 | "eslint.format.enable": true, 34 | "eslint.codeActionsOnSave.mode": "all", 35 | "eslint.options": {}, 36 | "typescript.inlayHints.parameterNames.enabled": "all", 37 | "typescript.updateImportsOnFileMove.enabled": "always", 38 | "typescript.suggest.autoImports": true, 39 | "volar.takeOverMode.extension": "Vue.volar", 40 | "volar.format.initialIndent": { 41 | "html": true 42 | }, 43 | "vue.autoInsert.dotValue": true, 44 | "vue.complete.normalizeComponentImportName": true, 45 | "vue.inlayHints.optionsWrapper": true, 46 | "vue.updateImportsOnFileMove.enabled": true, 47 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hi@element-plus.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome to FORMKL contributing guide 2 | 3 | Thank you for investing your time in contributing to our project! 4 | 5 | Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable. 6 | 7 | Please make sure to read the [Contributing Guide](https://vietscript.org/learning/contribution-guide.html) before making a pull request. 8 | 9 | Thank you to all the people who already contributed to Vue! 10 | 11 | 12 | 13 | 14 | 15 | Made with [contrib.rocks](https://contrib.rocks). 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rim (Y Nguyen) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VietScript - Vietnamese Programming Language - Ngôn ngữ lập trình tiếng Việt 2 | 3 | VietScript - Ngôn ngữ lập trình tiếng Việt, là một ngôn ngữ lập trình mã nguồn mở được viết hoàn toàn bằng TypeScript và hỗ trợ cú pháp tiếng Việt với dấu cách. VietScript được thiết kế để giúp cho người Việt mới bắt đầu học lập trình có thể dễ dàng học và sử dụng ngôn ngữ lập trình. 4 | 5 | ## Bắt đầu sử dụng 6 | 7 | Mời bạn xem tài liệu tại [vietscript.org](https://vietscript.org)! 8 | 9 | Xem demo tại: [sandbox.vietscript.org](https://sandbox.vietscript.org) 10 | 11 | 12 | ## Cài đặt và thử nghiệm 13 | Vietscript được phát triển theo phương pháp TDD (Test Driven Development), các trường hợp sử dụng trong ngôn ngữ được viết thành các test case và được đặt trong các thư mục `__test__`. Để chạy các test case, bạn cần cài đặt NodeJS và PNPM, sau đó các chạy lệnh sau: 14 | 15 | ```bash 16 | # Cài đặt PNPM 17 | npm install -g pnpm 18 | 19 | # Cài đặt các thư viện 20 | pnpm install 21 | 22 | # Chạy unit test 23 | pnpm test 24 | ``` 25 | 26 | ## Lý thuyết 27 | VietScript là ngôn ngữ thông dịch, nó được chuyển đổi thành mã nguồn JavaScript và chạy trên trình duyệt. JavaScript cũng từng là ngôn ngữ thông dịch (bây giờ thì là biên dịch tức thời - JIT compiled), nó không được chuyển đổi ngay lập tức thành mã máy và sẽ được thông dịch từng dòng một trước khi nó được thực thi. 28 | 29 | Cú pháp của VietScript giống JavaScript 96%, chỉ khác là nó hỗ trợ tiếng Việt :D 30 | 31 | ## Tiến độ 32 | Dự án được viết bằng Typescript thuần và không sử dụng thư viện ngoài, trừ các công cụ hỗ trợ đóng gói và kiểm thử. Hiện tại dự án đang trong giai đoạn phát triển và chưa thể sử dụng được. Dự án rất cần sự đóng góp từ cộng đồng để hoàn thiện :D 33 | 34 | 35 | ## Đóng góp 36 | 37 | Mọi người có thể đọc [hướng dẫn cách đóng góp dự án](https://vietscript.org/learning/contribution-guide.html) trước khi tạo một pull request. 38 | 39 | Cảm ơn tất cả mọi người đã đóng góp cho dự án VietScript - Ngôn ngữ lập trình tiếng Việt tại đây! 40 | 41 | 42 | 43 | 44 | 45 | Tạo ra bởi [contrib.rocks](https://contrib.rocks). 46 | 47 | ## License 48 | 49 | [MIT](https://opensource.org/licenses/MIT) 50 | 51 | Copyright (c) 2022-present, Nguyen Huu Nguyen Y 52 | -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/@theme_index.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.49_@algolia+client-search@4.19.1_@types+node@18.17.14_search-insights@2.8.1/node_modules/vitepress/dist/client/theme-default/index.js"], 4 | "sourcesContent": ["import './styles/fonts.css';\nimport './styles/vars.css';\nimport './styles/base.css';\nimport './styles/utils.css';\nimport './styles/components/custom-block.css';\nimport './styles/components/vp-code.css';\nimport './styles/components/vp-code-group.css';\nimport './styles/components/vp-doc.css';\nimport './styles/components/vp-sponsor.css';\nimport VPBadge from './components/VPBadge.vue';\nimport Layout from './Layout.vue';\nimport NotFound from './NotFound.vue';\nexport { default as VPHomeHero } from './components/VPHomeHero.vue';\nexport { default as VPHomeFeatures } from './components/VPHomeFeatures.vue';\nexport { default as VPHomeSponsors } from './components/VPHomeSponsors.vue';\nexport { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue';\nexport { default as VPTeamPage } from './components/VPTeamPage.vue';\nexport { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue';\nexport { default as VPTeamPageSection } from './components/VPTeamPageSection.vue';\nexport { default as VPTeamMembers } from './components/VPTeamMembers.vue';\nconst theme = {\n Layout,\n NotFound,\n enhanceApp: ({ app }) => {\n app.component('Badge', VPBadge);\n }\n};\nexport default theme;\n"], 5 | "mappings": ";AAAA,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO,aAAa;AACpB,OAAO,YAAY;AACnB,OAAO,cAAc;AACrB,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAqC;AAC9C,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAkC;AAC3C,SAAoB,WAAXA,gBAAoC;AAC7C,SAAoB,WAAXA,gBAAgC;AACzC,IAAM,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,EACA,YAAY,CAAC,EAAE,IAAI,MAAM;AACrB,QAAI,UAAU,SAAS,OAAO;AAAA,EAClC;AACJ;AACA,IAAO,wBAAQ;", 6 | "names": ["default"] 7 | } 8 | -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "5623e859", 3 | "browserHash": "47fba15a", 4 | "optimized": { 5 | "vue": { 6 | "src": "../../../../node_modules/.pnpm/vue@3.3.4/node_modules/vue/dist/vue.runtime.esm-bundler.js", 7 | "file": "vue.js", 8 | "fileHash": "7e660d2b", 9 | "needsInterop": false 10 | }, 11 | "@theme/index": { 12 | "src": "../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.49_@algolia+client-search@4.19.1_@types+node@18.17.14_search-insights@2.8.1/node_modules/vitepress/dist/client/theme-default/index.js", 13 | "file": "@theme_index.js", 14 | "fileHash": "04feec2d", 15 | "needsInterop": false 16 | } 17 | }, 18 | "chunks": {} 19 | } -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: "Vietscript - Ngôn ngữ lập trình tiếng Việt", 6 | description: "Một ngôn ngữ lập trình mã nguồn mở hỗ trợ cú pháp tiếng Việt với dấu cách.", 7 | themeConfig: { 8 | // https://vitepress.dev/reference/default-theme-config 9 | nav: [ 10 | { text: "Home", link: "/" }, 11 | { text: "Examples", link: "/markdown-examples" }, 12 | ], 13 | 14 | sidebar: [ 15 | { 16 | text: "Bắt đầu", 17 | items: [ 18 | { text: "Giới thiệu", link: "/introduction" }, 19 | { text: "Bắt đầu ngay", link: "/getting-started" }, 20 | ], 21 | }, 22 | { 23 | text: "Hướng dẫn sử dụng", 24 | items: [ 25 | { text: "Biến - Kiểu dữ liệu", link: "/basics/variables" }, 26 | { text: "Toán tử", link: "/basics/operators" }, 27 | { text: "Định nghĩa và thực thi hàm", link: "/basics/function" }, 28 | { text: "Câu lệnh điều kiện", link: "/basics/if" }, 29 | { text: "Câu lệnh lặp", link: "/basics/for" }, 30 | { text: "Câu lệnh khi mà", link: "/basics/do-while" }, 31 | { text: "Câu lệnh duyệt", link: "/basics/switch-case" }, 32 | ], 33 | }, 34 | ], 35 | 36 | socialLinks: [{ icon: "github", link: "https://github.com/vuejs/vitepress" }], 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /docs/api-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Runtime API Examples 6 | 7 | This page demonstrates usage of some of the runtime APIs provided by VitePress. 8 | 9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: 10 | 11 | ```md 12 | 17 | 18 | ## Results 19 | 20 | ### Theme Data 21 |
{{ theme }}
22 | 23 | ### Page Data 24 |
{{ page }}
25 | 26 | ### Page Frontmatter 27 |
{{ frontmatter }}
28 | ``` 29 | 30 | 35 | 36 | ## Results 37 | 38 | ### Theme Data 39 |
{{ theme }}
40 | 41 | ### Page Data 42 |
{{ page }}
43 | 44 | ### Page Frontmatter 45 |
{{ frontmatter }}
46 | 47 | ## More 48 | 49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). 50 | -------------------------------------------------------------------------------- /docs/basics/do-while.md: -------------------------------------------------------------------------------- 1 | # Câu lệnh khi mà 2 | 3 | Câu lệnh lặp khi mà trong VieLang cho phép bạn lặp lại một khối mã chừng nào điều kiện vẫn còn đúng. Đây là một công cụ mạnh mẽ để thực thi mã lặp đi lặp lại dựa trên một điều kiện nhất định. 4 | 5 | 6 | ## Cú pháp 7 | Cú pháp cho câu lệnh lặp khi mà như sau: 8 | 9 | ```css 10 | khi mà (điều_kiện) { 11 | // Khối mã sẽ được lặp lại chừng nào điều kiện còn đúng 12 | } 13 | 14 | ``` 15 | 16 | 17 | ## Tổng kết 18 | 19 | Câu lệnh lặp khi mà trong VieLang là một công cụ hữu ích để lặp lại một khối mã chừng nào điều kiện còn đúng. Bằng cách sử dụng câu lệnh này, bạn có thể dễ dàng thực thi các hành động lặp đi lặp lại một cách hiệu quả. -------------------------------------------------------------------------------- /docs/basics/for.md: -------------------------------------------------------------------------------- 1 | # Câu lệnh lặp 2 | 3 | Câu lệnh lặp `lặp` trong VieLang cho phép bạn thực thi một khối mã lặp đi lặp lại trong một khoảng thời gian xác định. Đây là một công cụ hữu ích để lặp qua một phạm vi giá trị cụ thể hoặc để thực thi mã một số lần nhất định. 4 | 5 | 6 | 7 | ## Cú pháp 8 | Cú pháp cho câu lệnh lặp như sau: 9 | 10 | ```css 11 | lặp (khởi_tạo ; điều_kiện ; cập_nhật) { 12 | // Khối mã sẽ được lặp lại dựa trên các điều kiện xác định 13 | } 14 | 15 | ``` 16 | ### Ví dụ 17 | Dưới đây là một ví dụ về cách sử dụng câu lệnh lặp trong VieLang: 18 | 19 | ```css 20 | lặp (khai báo i = 0; i < 5; i++) { 21 | in ra (i); 22 | } 23 | ``` 24 | 25 | 26 | ## Tổng kết 27 | 28 | Câu lệnh lặp `lặp` trong VieLang là một công cụ mạnh mẽ và linh hoạt để lặp qua một phạm vi giá trị hoặc thực thi mã một số lần nhất định. Bằng cách sử dụng câu lệnh này, bạn có thể dễ dàng kiểm soát luồng thực thi của chương trình và thực hiện các hành động lặp đi lặp lại một cách hiệu quả. -------------------------------------------------------------------------------- /docs/basics/function.md: -------------------------------------------------------------------------------- 1 | # Định nghĩa và thực thi hàm 2 | 3 | Hàm VieLang là một khối mã được thiết kế để thực hiện một tác vụ cụ thể. Một hàm Vielang được thực thi khi gọi nó (call it) 4 | 5 | ## Cú pháp khai báo hàm 6 | 7 | Hàm `Vielang` được xác định bằng từ khóa `hàm`, theo sau là tên, theo sau là dấu ngoặc đơn `()`. 8 | 9 | Tên hàm có thể chứa các chữ cái tiếng việt có dấu cách (giống với tên biến) 10 | 11 | Các dấu ngoặc đơn có thể bao gồm các tên tham số được phân tách bằng dấu phẩy: (tham số một, tham số hai, ...) 12 | 13 | Mã được hàm thực thi được đặt bên trong dấu ngoặc nhọn: {} 14 | 15 | ```js 16 | hàm tính tuổi(năm sinh) { 17 | // code to be executed 18 | 19 | } 20 | ``` 21 | 22 | ## Gọi hàm 23 | 24 | Hàm sẽ được thực thi khi gọi hàm bằng cú pháp `()` 25 | 26 | ```js 27 | hàm tính tuổi(năm sinh) { 28 | // code to be executed 29 | 30 | } 31 | 32 | tính tuổi() 33 | ``` 34 | 35 | ## Hàm trả về 36 | 37 | Sử dụng từ khoá `trả về` để trả về 1 giá trị của hàm 38 | 39 | ```js 40 | hàm tính tuổi(năm sinh) { 41 | khai báo tuổi = 2024 - năm sinh 42 | trả về tuổi 43 | } 44 | 45 | ``` -------------------------------------------------------------------------------- /docs/basics/if.md: -------------------------------------------------------------------------------- 1 | # Câu lệnh điều kiện 2 | 3 | Câu lệnh điều kiện trong VieLang cho phép bạn thực thi mã dựa trên việc một điều kiện là đúng hay sai. Câu lệnh điều kiện chính được sử dụng trong VieLang là câu lệnh nếu. 4 | 5 | ## Cú pháp 6 | Cú pháp cho câu lệnh nếu như sau: 7 | 8 | ```js 9 | nếu (điều_kiện) { 10 | // Khối mã sẽ được thực thi nếu điều kiện đúng 11 | } 12 | ``` 13 | 14 | **Ví dụ** 15 | Dưới đây là một ví dụ về cách sử dụng câu lệnh nếu trong VieLang: 16 | 17 | ```js 18 | 19 | nếu (a > 2) { 20 | // Thực hiện hành động khi a lớn hơn 2 21 | } 22 | ``` 23 | Trong ví dụ trên, nếu giá trị của a lớn hơn 2, khối mã trong dấu ngoặc nhọn {} sẽ được thực thi. 24 | 25 | 26 | ## Giải thích 27 | `nếu`: Từ khóa bắt đầu một câu lệnh điều kiện. 28 | (điều_kiện): Một biểu thức được kiểm tra. Nếu biểu thức này đánh giá là đúng, khối mã bên trong sẽ được thực thi. 29 | {}: Dấu ngoặc nhọn bao quanh khối mã sẽ được thực thi nếu điều kiện là đúng. 30 | ## Câu lệnh nếu kết hợp với "ngược lại" 31 | Bạn có thể kết hợp câu lệnh nếu với câu lệnh khác để xử lý trường hợp khi điều kiện không đúng. 32 | 33 | Cú pháp 34 | 35 | ```js 36 | nếu (a>2){ 37 | // Thực thi code khi a lớn hơn 2 38 | } ngược lại { 39 | // Thực thi các trường hợp khác 40 | } 41 | 42 | ``` 43 | 44 | ## Câu lệnh nếu lồng nhau 45 | Bạn cũng có thể sử dụng các câu lệnh nếu lồng nhau để kiểm tra nhiều điều kiện. 46 | 47 | Ví dụ: 48 | ```js 49 | nếu (a > 2) { 50 | nếu (b > 5) { 51 | // Thực hiện hành động khi a lớn hơn 2 và b lớn hơn 5 52 | } 53 | } 54 | 55 | ``` -------------------------------------------------------------------------------- /docs/basics/operators.md: -------------------------------------------------------------------------------- 1 | # Toán tử 2 | 3 | Vielang hỗ trợ các kiểu toán tử sau: - Toán tử số học - Toán tử gán - Toán tử so sánh - Toán tử logic 4 | 5 | ## Toán tử số học 6 | 7 | Ví dụ: 8 | 9 | ```js 10 | khai báo a = 5; 11 | khai báo x = 12 * a 12 | ``` 13 | 14 | | Toán tử | Thông tin | 15 | | ------- | ------------- | 16 | | + | Cộng | 17 | | - | Trừ | 18 | | \* | Nhân | 19 | | / | Chia | 20 | | % | Chia lấy dư | 21 | | \*\* | Luỹ thừa | 22 | | ++ | Tăng 1 đơn vị | 23 | | -- | Giảm 1 dơn vị | 24 | 25 | ## Toán tử gán 26 | 27 | ```js 28 | khai báo x = 10; 29 | x += 5; 30 | ``` 31 | 32 | | Toán tử | Ví dụ | Tương đương với | 33 | | ------- | --------- | --------------- | 34 | | = | x = y | x = y | 35 | | += | x += y | x = x + y | 36 | | -= | x -= y | x = x - y | 37 | | \*= | x \*= y | x = x \* y | 38 | | /= | x /= y | x = x / y | 39 | | %= | x %= y | x = x % y | 40 | | \*\*= | x \*\*= y | x = x \*\* y | 41 | 42 | ## Toán tử so sánh 43 | 44 | | Toán tử | Thông tin | 45 | | ------- | ---------------------------- | 46 | | == | bằng giá trị | 47 | | === | bằng giá trị và kiểu dữ liệu | 48 | | != | khác giá trị | 49 | | !== | khác giá trị và kiểu dữ liệu | 50 | | \> | lớn hơn | 51 | | \< | nhỏ hơn | 52 | | \>= | lớn hơn hoặc bằng | 53 | | \<= | nhỏ hơn hoặc bằng | 54 | | \? | Toán tử bậc 3 | 55 | 56 | ## Toán tử logic 57 | 58 | | Toán tử | Thông tin | 59 | | ------- | --------- | 60 | | \&\& | và | 61 | | \|\| | hoặc | 62 | | ! | phủ định | 63 | -------------------------------------------------------------------------------- /docs/basics/switch-case.md: -------------------------------------------------------------------------------- 1 | # Câu lệnh duyệt điều kiện 2 | 3 | Câu lệnh duyệt trong VieLang cho phép bạn kiểm tra giá trị của một biến và thực thi các khối mã khác nhau dựa trên giá trị đó. Đây là một công cụ mạnh mẽ để thay thế cho các câu lệnh nếu...khác phức tạp khi bạn cần kiểm tra nhiều giá trị khác nhau của cùng một biến. 4 | 5 | 6 | ## Cú pháp 7 | 8 | ```css 9 | duyệt (biến) { 10 | trường hợp giá_trị_1: 11 | // Khối mã sẽ được thực thi nếu biến có giá trị là giá_trị_1 12 | trường hợp giá_trị_2: 13 | // Khối mã sẽ được thực thi nếu biến có giá trị là giá_trị_2 14 | ... 15 | mặc định: 16 | // Khối mã sẽ được thực thi nếu biến không khớp với bất kỳ giá trị nào ở trên 17 | } 18 | ``` 19 | 20 | ### Ví dụ 21 | Dưới đây là một ví dụ về cách sử dụng câu lệnh duyệt trong VieLang: 22 | 23 | 24 | ```css 25 | duyệt (tuổi_tác) { 26 | trường hợp 20: 27 | in ra ("bạn 20 tuổi"); 28 | trường hợp 12: 29 | in ra ("bạn 12 tuổi"); 30 | mặc định: 31 | in ra ("mặc định ở"); 32 | } 33 | 34 | ``` 35 | 36 | 37 | ## Tổng kết 38 | Câu lệnh duyệt trong VieLang là một công cụ mạnh mẽ để kiểm tra giá trị của một biến và thực thi các khối mã khác nhau dựa trên giá trị đó. Bằng cách sử dụng câu lệnh này, bạn có thể đơn giản hóa mã nguồn khi cần kiểm tra nhiều giá trị khác nhau của cùng một biến. -------------------------------------------------------------------------------- /docs/basics/variables.md: -------------------------------------------------------------------------------- 1 | 2 | # Biến - Kiểu dữ liệu 3 | 4 | Trang này sẽ nói về cú pháp của ***VieLang***, cách khai báo biến và các kiểu dữ liệu 5 | 6 | ## Tổng quan 7 | 8 | Cú pháp VieLang dựa trên ngôn ngữ lập trình `Javascript`, do đó dễ dàng tiếp cận cho người đã từng lập trình hay kể cả chưa biết gì về lập trình. Vì ngôn ngữ hỗ trợ `tiếng Việt`, một ngôn ngữ mẹ đẻ của người Việt 9 | 10 | So sánh 11 | 12 | - Javascript: 13 | 14 | ```js 15 | let name = 'Viên'; 16 | ``` 17 | 18 | - Vielang: 19 | 20 | ```js 21 | khai báo tên = 'Viên' // Hỗ trợ tiếng việt cho cả tên biến 22 | 23 | ``` 24 | 25 | Thử ngay tại [https://vietscript.dev/](https://vietscript.dev/). 26 | 27 | ## Khai báo 28 | 29 | VieLang có 2 cách để khai báo biến: 30 | 31 | 32 | `khai báo`: 33 | - Sử dụng từ khoá `khai báo` để khai báo một giá trị 34 | 35 | `hằng số`: 36 | - Sử dụng từ khoá `hằng số` để khai báo một giá trị chỉ đọc, không được gán lại 37 | 38 | ## Biến 39 | 40 | - Tên biến trong **VieLang** được khai báo bằng chữ, hỗ trợ tiếng Việt và dấu cách 41 | 42 | - Ví dụ 43 | 44 | ```js 45 | 46 | khai báo tên = "Viên" 47 | khai báo tuổi = 22 48 | 49 | ``` 50 | 51 | ## Kiểu dữ liệu 52 | 53 | - Có 5 kiểu dữ liệu nguyên thuỷ: 54 | 55 | - Boolean: `đúng`, `sai` 56 | - null: `vô giá trị` 57 | - undefined: `không xác định` 58 | - Số: số nguyên hoặc số thập phân, Ví dụ: `20`, `3.14` 59 | - Chuỗi: một chuỗi kí tự đại diện cho văn bản, Ví dụ: `'vienhuynh'` 60 | 61 | - Mảng 62 | - Đối tượng (object) -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Bắt đầu ngay 2 | 3 | ## Sử dụng trình duyệt của VietScript 4 | Playground của VietScript là một môi trường trực tuyến nơi bạn có thể viết, thử nghiệm và chạy mã VietScript trực tiếp trên trình duyệt mà không cần cài đặt thêm bất kỳ phần mềm nào. 5 | 6 | 7 | ## Cài đặt và thử nghiệm 8 | VietScript được phát triển theo phương pháp TDD (Test Driven Development), các trường hợp sử dụng trong ngôn ngữ được viết thành các test case và được đặt trong các thư mục `__test__`. Để chạy các test case, bạn cần cài đặt NodeJS và PNPM, sau đó các chạy lệnh sau: 9 | 10 | ```bash 11 | # Cài đặt PNPM 12 | npm install -g pnpm 13 | 14 | # Cài đặt các thư viện 15 | pnpm install 16 | 17 | # Chạy unit test 18 | pnpm test 19 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Vietscript - Ngôn ngữ lập trình tiếng Việt" 7 | tagline: "Một ngôn ngữ lập trình mã nguồn mở hỗ trợ cú pháp tiếng Việt với dấu cách." 8 | actions: 9 | - theme: brand 10 | text: VietScript là gì? 11 | link: /introduction 12 | - theme: alt 13 | text: Playground 14 | link: /playground 15 | 16 | features: 17 | - title: Dễ dàng sử dụng 18 | details: Cú pháp của VieLang giống JavaScript 96%, chỉ khác là nó hỗ trợ tiếng Việt :D 19 | - title: Được viết bằng Typescript thuần 20 | details: Dự án được viết bằng Typescript thuần và không sử dụng thư viện ngoài, trừ các công cụ hỗ trợ đóng gói và kiểm thử. 21 | 22 | --- 23 | 24 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Giới thiệu 2 | 3 | ## Lý thuyết 4 | 5 | VieLang là ngôn ngữ thông dịch, nó được chuyển đổi thành mã nguồn JavaScript và chạy trên trình duyệt. JavaScript cũng là ngôn ngữ thông dịch, nó không được chuyển đổi ngay lập tức thành mã máy và sẽ được thông dịch từng dòng một trước khi nó được thực thi. 6 | 7 | Cú pháp của VieLang giống JavaScript 96%, chỉ khác là nó hỗ trợ tiếng Việt :D 8 | 9 | 10 | ## Đóng góp 11 | 12 | Mọi người có thể đọc [hướng dẫn cách đóng góp dự án](https://vienhuynh.dev) trước khi tạo một pull request. 13 | 14 | Cảm ơn tất cả mọi người đã đóng góp cho dự án VieLang - Ngôn ngữ lập trình tiếng Việt tại đây! 15 | 16 | 17 | Tạo ra bởi [Vien Huynh](https://vienhuynh.dev). 18 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "@nrwl/nx-cloud", 5 | "options": { 6 | "cacheableOperations": ["build", "typecheck", "test"], 7 | "accessToken": "NzA3YzM4NWMtODk0Ni00M2I5LWJkMjItMzU1MjMzNGM0MGY4fHJlYWQtd3JpdGU=" 8 | } 9 | } 10 | }, 11 | "namedInputs": { 12 | "noMarkDown": ["!{projectRoot}/**/*.md"] 13 | }, 14 | "targetDefaults": { 15 | "build": { 16 | "inputs": ["noMarkDown", "^noMarkDown"], 17 | "dependsOn": ["^build"] 18 | }, 19 | "typecheck": { 20 | "inputs": ["noMarkDown", "^noMarkDown"], 21 | "dependsOn": ["^build"] 22 | }, 23 | "test": { 24 | "inputs": ["noMarkDown", "^noMarkDown"], 25 | "dependsOn": ["^build"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "type": "module", 4 | "description": "Vietnamese programming language - Ngôn ngữ lập trình tiếng Việt.", 5 | "packageManager": "pnpm@8.5.0", 6 | "scripts": { 7 | "nx": "nx", 8 | "lint": "eslint . --ext .ts,.js --fix", 9 | "build": "nx run-many --target=build --all", 10 | "typecheck": "nx run-many --target=typecheck --all", 11 | "test": "vitest run", 12 | "dev": "vitest --ui", 13 | "dev:docs": "vitepress dev docs", 14 | "serve:docs": "vitepress serve docs", 15 | "build:docs": "vitepress build docs", 16 | "prepare-husky": "husky install", 17 | "release": "release-it", 18 | "docs:dev": "vitepress dev docs", 19 | "docs:build": "vitepress build docs", 20 | "docs:preview": "vitepress preview docs" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/imrim12/vietscript.git" 25 | }, 26 | "keywords": [ 27 | "language", 28 | "vietscript", 29 | "vietnamese", 30 | "programming language" 31 | ], 32 | "author": "imrim12", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/imrim12/vietscript/issues" 36 | }, 37 | "homepage": "https://github.com/imrim12/vietscript#readme", 38 | "workspaces": [ 39 | "packages/**", 40 | "sandbox" 41 | ], 42 | "dependencies": { 43 | "@vietscript/parser": "workspace:*" 44 | }, 45 | "devDependencies": { 46 | "@babel/generator": "^7.22.10", 47 | "@commitlint/cli": "^17.2.0", 48 | "@commitlint/config-conventional": "^17.2.0", 49 | "@nrwl/nx-cloud": "16.3.0", 50 | "@nrwl/workspace": "16.6.0", 51 | "@release-it/conventional-changelog": "^5.1.1", 52 | "@types/babel-generator": "^6.25.5", 53 | "@types/babel__generator": "^7.6.8", 54 | "@types/lodash": "^4.14.187", 55 | "@types/node": "^18.11.8", 56 | "@typescript-eslint/eslint-plugin": "^5.38.0", 57 | "@typescript-eslint/parser": "^5.38.0", 58 | "@vitejs/plugin-vue": "^3.1.2", 59 | "@vitejs/plugin-vue-jsx": "^2.0.1", 60 | "@vitest/ui": "^0.24.5", 61 | "@vue/test-utils": "^2.2.1", 62 | "eslint": "^8.23.1", 63 | "eslint-plugin-import": "^2.26.0", 64 | "eslint-plugin-prettier": "^4.2.1", 65 | "eslint-plugin-unicorn": "^45.0.2", 66 | "husky": "^8.0.2", 67 | "jsdom": "^20.0.2", 68 | "nx": "16.6.0", 69 | "prettier": "^2.8.1", 70 | "release-it": "^15.5.0", 71 | "rimraf": "^3.0.2", 72 | "ts-node": "^10.9.1", 73 | "typescript": "^4.9.4", 74 | "vitepress": "1.0.0-alpha.49", 75 | "vitest": "^0.24.5", 76 | "vue": "^3.2.41", 77 | "vue-tsc": "^1.0.9" 78 | } 79 | } -------------------------------------------------------------------------------- /packages/parser/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | extends: "../../.eslintrc.cjs", 4 | }; 5 | -------------------------------------------------------------------------------- /packages/parser/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /packages/parser/.npmignore: -------------------------------------------------------------------------------- 1 | /__test__ 2 | /logs 3 | /node_modules 4 | /src 5 | 6 | .gitignore 7 | .minify.json 8 | .npmignore 9 | -------------------------------------------------------------------------------- /packages/parser/README.md: -------------------------------------------------------------------------------- 1 | # VietScript - Vietnamese Programming Language - Ngôn ngữ lập trình tiếng Việt 2 | 3 | VietScript - Ngôn ngữ lập trình tiếng Việt, là một ngôn ngữ lập trình mã nguồn mở được viết hoàn toàn bằng TypeScript và hỗ trợ cú pháp tiếng Việt. VietScript được thiết kế để giúp cho người mới bắt đầu học lập trình có thể dễ dàng học và sử dụng ngôn ngữ lập trình. 4 | 5 | ## Getting Started 6 | 7 | Please follow the documentation at [vietscript.org](https://vietscript.org)! 8 | 9 | View sandbox: [sandbox.vietscript.org](https://sandbox.vietscript.org) 10 | 11 | ## Contribution 12 | 13 | Please make sure to read the [Contributing Guide](https://vietscript.org/learning/contribution-guide.html) before making a pull request. 14 | 15 | Thank you to all the people who already contributed to the VietScript - Vietnamese Programming Language project! 16 | 17 | 18 | 19 | 20 | 21 | Made with [contrib.rocks](https://contrib.rocks). 22 | 23 | ## License 24 | 25 | [MIT](https://opensource.org/licenses/MIT) 26 | 27 | Copyright (c) 2022-present, Nguyen Huu Nguyen Y 28 | -------------------------------------------------------------------------------- /packages/parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vietscript/parser", 3 | "version": "0.0.1", 4 | "description": "Vietnamese programming language - Ngôn ngữ lập trình tiếng Việt.", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/index.js", 9 | "require": "./dist/index.cjs" 10 | } 11 | }, 12 | "main": "./dist/index.js", 13 | "types": "./dist/types/index.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "typecheck": "tsc --noEmit", 19 | "build": "rimraf ./dist && vite build && cp ./dist/index.umd.cjs ./public/index.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/imrim12/vietscript.git", 24 | "directory": "packages/parser" 25 | }, 26 | "publishConfig": { 27 | "access": "public", 28 | "registry": "https://registry.npmjs.org" 29 | }, 30 | "keywords": [ 31 | "language", 32 | "vietscript", 33 | "vietnamese", 34 | "programming language" 35 | ], 36 | "author": "imrim12", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/imrim12/vietscript/issues" 40 | }, 41 | "homepage": "https://github.com/imrim12/vietscript#readme", 42 | "devDependencies": { 43 | "@vietscript/shared": "workspace:*", 44 | "@types/node": "^18.14.1", 45 | "@vitest/ui": "^0.24.5", 46 | "lodash": "^4.17.21", 47 | "rimraf": "^3.0.2", 48 | "typescript": "^4.9.4", 49 | "vite": "^4.0.4", 50 | "vite-plugin-dts": "^1.6.6", 51 | "vitest": "^0.28.3" 52 | } 53 | } -------------------------------------------------------------------------------- /packages/parser/src/__test__/identifier-match-keyword.test.ts: -------------------------------------------------------------------------------- 1 | import { Program } from "@parser/nodes/Program"; 2 | 3 | import parser from "../setup-test"; 4 | import toPlainObject from "../toPlainObject"; 5 | 6 | describe("identifier-match-keyword.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse(`khai báo một lớp gì đó = 1`, Program); 9 | 10 | expect(toPlainObject(result)).toStrictEqual({ 11 | type: "Program", 12 | body: [ 13 | { 14 | type: "VariableDeclaration", 15 | declarations: [ 16 | { 17 | type: "VariableDeclarator", 18 | id: { 19 | type: "Identifier", 20 | name: "_m7897t_l7899p_g236_273243", 21 | }, 22 | init: { 23 | type: "NumericLiteral", 24 | value: 1, 25 | extra: { 26 | rawValue: 1, 27 | raw: "1", 28 | }, 29 | start: 25, 30 | end: 26, 31 | }, 32 | }, 33 | ], 34 | kind: "var", 35 | }, 36 | ], 37 | }); 38 | }); 39 | 40 | it("should show the syntax error", () => { 41 | expect(() => { 42 | parser.parse("khai báo lớp gì đó = 1", Program); 43 | }).toThrowError(/Unexpected token: "lớp", cannot use keyword "lớp" for the beginning of the identifer/); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/parser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser"; 2 | 3 | const parser = new Parser(); 4 | 5 | export default parser; 6 | 7 | export { Tokenizer } from "./tokenizer"; 8 | export { Parser } from "./parser"; 9 | 10 | if (typeof window !== "undefined") { 11 | customElements.define( 12 | "vi-script", 13 | class extends HTMLElement { 14 | constructor() { 15 | super(); 16 | } 17 | 18 | connectedCallback() { 19 | setTimeout(() => { 20 | const script = this.childNodes.item(0).textContent?.trim(); 21 | 22 | this.innerHTML = ""; 23 | 24 | if (script) { 25 | // executor.execute(script); 26 | } 27 | }); 28 | } 29 | }, 30 | ); 31 | 32 | // @ts-ignore 33 | window.VietScript = { parser }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/Program.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Statement } from "./statements/Statement"; 4 | import { StatementList } from "./statements/StatementList"; 5 | 6 | export class Program { 7 | type = "Program"; 8 | 9 | body: Array; 10 | 11 | constructor(parser: Parser) { 12 | this.body = new StatementList(parser).body; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/ClassDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Keyword } from "@vietscript/shared"; 4 | 5 | import { ClassBody } from "./class/ClassBody"; 6 | 7 | export class ClassDeclaration { 8 | type = "ClassDeclaration"; 9 | 10 | id: Identifier; 11 | 12 | superClass: null | Identifier = null; 13 | 14 | body: ClassBody; 15 | 16 | constructor(parser: Parser) { 17 | parser.eat(Keyword.CLASS); 18 | 19 | this.id = new Identifier(parser); 20 | 21 | if (parser.lookahead?.type === "(") { 22 | parser.eat("("); 23 | 24 | this.superClass = new Identifier(parser); 25 | 26 | parser.eat(")"); 27 | } 28 | 29 | this.body = new ClassBody(parser); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/Declaration.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { ClassDeclaration } from "./ClassDeclaration"; 5 | import { FunctionDeclaration } from "./FunctionDeclaration"; 6 | import { VariableDeclaration } from "./VariableDeclaration"; 7 | import { ExportDeclaration } from "./export/ExportDeclaration"; 8 | import { ImportDeclaration } from "./import/ImportDeclaration"; 9 | 10 | export class Declaration { 11 | [key: string]: any; 12 | 13 | constructor(parser: Parser) { 14 | switch (parser.lookahead?.type) { 15 | case Keyword.VAR: 16 | case Keyword.LET: 17 | case Keyword.CONST: { 18 | Object.assign(this, new VariableDeclaration(parser)); 19 | break; 20 | } 21 | case Keyword.ASYNC: 22 | case Keyword.FUNCTION: { 23 | Object.assign(this, new FunctionDeclaration(parser)); 24 | break; 25 | } 26 | case Keyword.CLASS: { 27 | Object.assign(this, new ClassDeclaration(parser)); 28 | break; 29 | } 30 | case Keyword.EXPORT: { 31 | Object.assign(this, new ExportDeclaration(parser)); 32 | break; 33 | } 34 | case Keyword.IMPORT: { 35 | Object.assign(this, new ImportDeclaration(parser)); 36 | break; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/FunctionDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | import { ParameterList } from "./ParameterList"; 7 | 8 | export class FunctionDeclaration { 9 | type = "FunctionDeclaration"; 10 | 11 | id: Identifier; 12 | 13 | expression: boolean; 14 | 15 | generator: boolean; 16 | 17 | async: boolean; 18 | 19 | params: Array; 20 | 21 | body: BlockStatement; 22 | 23 | constructor(parser: Parser) { 24 | let isAsync = false; 25 | let isGenerator = false; 26 | 27 | if (parser.lookahead?.type === Keyword.ASYNC) { 28 | parser.eat(Keyword.ASYNC); 29 | isAsync = true; 30 | } 31 | 32 | parser.eat(Keyword.FUNCTION); 33 | 34 | if (parser.lookahead?.type === "*") { 35 | parser.eat("*"); 36 | isGenerator = true; 37 | } 38 | 39 | const name = String(new Identifier(parser).name); 40 | 41 | parser.eat("("); 42 | 43 | const params: Array = new ParameterList(parser, ")").parameters; 44 | 45 | parser.eat(")"); 46 | 47 | const body = new BlockStatement(parser); 48 | 49 | this.id = { 50 | type: Keyword.IDENTIFIER, 51 | name, 52 | }; 53 | this.expression = false; 54 | this.generator = isGenerator; 55 | this.async = isAsync; 56 | this.params = params; 57 | this.body = body; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/ParameterList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Identifier } from "../identifier/Identifier"; 4 | 5 | export class ParameterList { 6 | parameters: Array = []; 7 | 8 | constructor(parser: Parser, stopToken = ")") { 9 | while (parser.lookahead?.type !== stopToken) { 10 | this.parameters.push(new Identifier(parser)); 11 | 12 | if (parser.lookahead?.type !== stopToken) { 13 | parser.eat(","); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/VariableDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { VariableDeclarator } from "./VariableDeclarator"; 5 | 6 | export class VariableDeclaration { 7 | type = "VariableDeclaration"; 8 | 9 | declarations: Array; 10 | 11 | kind: "var" | "let" | "const"; 12 | 13 | constructor(parser: Parser) { 14 | let kind: "var" | "let" | "const" = "var"; 15 | let isConstant = false; 16 | 17 | switch (parser.lookahead?.type) { 18 | case Keyword.VAR: { 19 | parser.eat(Keyword.VAR); 20 | kind = "var"; 21 | 22 | break; 23 | } 24 | case Keyword.LET: { 25 | parser.eat(Keyword.LET); 26 | kind = "let"; 27 | 28 | break; 29 | } 30 | case Keyword.CONST: { 31 | parser.eat(Keyword.CONST); 32 | kind = "const"; 33 | isConstant = true; 34 | 35 | break; 36 | } 37 | default: { 38 | throw new SyntaxError(`Unexpected token: "${parser.lookahead?.value}", expected a variable declarator!`); 39 | } 40 | } 41 | 42 | const declarations: Array = []; 43 | 44 | do { 45 | declarations.push(new VariableDeclarator(parser, isConstant)); 46 | // @ts-expect-error no overlap 47 | } while (parser.lookahead?.type === "," && parser.eat(",")); 48 | 49 | this.declarations = declarations; 50 | this.kind = kind; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/VariableDeclarator.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Expression } from "@parser/nodes/expressions/Expression"; 3 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class VariableDeclarator { 7 | type = "VariableDeclarator"; 8 | 9 | id: Identifier; 10 | 11 | init: Expression = { 12 | type: Keyword.IDENTIFIER, 13 | name: "undefined", 14 | }; 15 | 16 | constructor(parser: Parser, isConstant = false) { 17 | const identifier = new Identifier(parser); 18 | 19 | if (isConstant || parser.lookahead?.type === "=") { 20 | parser.eat("="); 21 | 22 | this.init = new Expression(parser); 23 | } 24 | 25 | this.id = identifier; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/__test__/declaration-class.test.ts: -------------------------------------------------------------------------------- 1 | import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | import { Declaration } from "../Declaration"; 6 | 7 | describe("declaration-class.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse( 10 | ` 11 | lớp Con Mèo (Động Vật) { 12 | số chân = 4 13 | bất đồng bộ kêu(số lần, hmm) { 14 | trả về "Meo meo" 15 | } 16 | } 17 | `, 18 | Declaration, 19 | ); 20 | 21 | expect(toPlainObject(result)).toStrictEqual({ 22 | type: "ClassDeclaration", 23 | id: { 24 | type: "Identifier", 25 | name: "_Con_M232o", 26 | }, 27 | superClass: { 28 | type: "Identifier", 29 | name: "_2727897ng_V7853t", 30 | }, 31 | body: { 32 | type: "ClassBody", 33 | body: [ 34 | { 35 | type: "ClassProperty", 36 | static: false, 37 | computed: false, 38 | key: { 39 | type: "Identifier", 40 | name: "_s7889_ch226n", 41 | }, 42 | value: { 43 | type: "NumericLiteral", 44 | value: 4, 45 | extra: { 46 | rawValue: 4, 47 | raw: "4", 48 | }, 49 | start: 41, 50 | end: 42, 51 | }, 52 | }, 53 | { 54 | type: "ClassMethod", 55 | static: false, 56 | computed: false, 57 | key: { 58 | type: "Identifier", 59 | name: "_k234u", 60 | }, 61 | kind: "method", 62 | id: null, 63 | generator: false, 64 | async: true, 65 | params: [ 66 | { 67 | type: "Identifier", 68 | name: "_s7889_l7847n", 69 | }, 70 | { 71 | type: "Identifier", 72 | name: "_hmm", 73 | }, 74 | ], 75 | body: { 76 | type: "BlockStatement", 77 | body: [ 78 | { 79 | type: "ReturnStatement", 80 | argument: { 81 | type: "StringLiteral", 82 | value: "Meo meo", 83 | extra: { 84 | rawValue: "Meo meo", 85 | raw: '"Meo meo"', 86 | }, 87 | start: 88, 88 | end: 97, 89 | }, 90 | }, 91 | ], 92 | directives: [], 93 | }, 94 | }, 95 | ], 96 | }, 97 | } as ClassDeclaration); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/__test__/declaration-export.test.ts: -------------------------------------------------------------------------------- 1 | import parser from "../../../setup-test"; 2 | import toPlainObject from "../../../toPlainObject"; 3 | import { Declaration } from "../Declaration"; 4 | import { ExportDeclaration } from "../export/ExportDeclaration"; 5 | 6 | describe("declaration-export.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse(`cho phép * từ "./test-path"`, Declaration); 9 | 10 | expect(toPlainObject(result)).toStrictEqual({ 11 | type: "ExportAllDeclaration", 12 | source: { 13 | type: "StringLiteral", 14 | value: "./test-path", 15 | extra: { 16 | rawValue: "./test-path", 17 | raw: '"./test-path"', 18 | }, 19 | start: 14, 20 | end: 27, 21 | }, 22 | } as ExportDeclaration); 23 | }); 24 | 25 | it("should parse the syntax normally", () => { 26 | const result = parser.parse(`cho phép { tính năng gì đấy } từ "./test-path"`, Declaration); 27 | 28 | expect(toPlainObject(result)).toStrictEqual({ 29 | type: "ExportNamedDeclaration", 30 | specifiers: [ 31 | { 32 | type: "ExportSpecifier", 33 | local: { 34 | type: "Identifier", 35 | name: "_t237nh_n259ng_g236_2737845y", 36 | }, 37 | exported: { 38 | type: "Identifier", 39 | name: "_t237nh_n259ng_g236_2737845y", 40 | }, 41 | }, 42 | ], 43 | source: { 44 | type: "StringLiteral", 45 | value: "./test-path", 46 | extra: { 47 | rawValue: "./test-path", 48 | raw: '"./test-path"', 49 | }, 50 | start: 33, 51 | end: 46, 52 | }, 53 | } as ExportDeclaration); 54 | }); 55 | 56 | it("should parse the syntax normally", () => { 57 | const result = parser.parse( 58 | `cho phép { 59 | tính năng cộng: phương thức cộng, 60 | tính năng chia: phương thức chia, 61 | phương thức trừ 62 | } từ "./test-path"`, 63 | Declaration, 64 | ); 65 | 66 | expect(toPlainObject(result)).toStrictEqual({ 67 | type: "ExportNamedDeclaration", 68 | specifiers: [ 69 | { 70 | type: "ExportSpecifier", 71 | local: { 72 | type: "Identifier", 73 | name: "_t237nh_n259ng_c7897ng", 74 | }, 75 | exported: { 76 | type: "Identifier", 77 | name: "_ph432417ng_th7913c_c7897ng", 78 | }, 79 | }, 80 | { 81 | type: "ExportSpecifier", 82 | local: { 83 | type: "Identifier", 84 | name: "_t237nh_n259ng_chia", 85 | }, 86 | exported: { 87 | type: "Identifier", 88 | name: "_ph432417ng_th7913c_chia", 89 | }, 90 | }, 91 | { 92 | type: "ExportSpecifier", 93 | local: { 94 | type: "Identifier", 95 | name: "_ph432417ng_th7913c_tr7915", 96 | }, 97 | exported: { 98 | type: "Identifier", 99 | name: "_ph432417ng_th7913c_tr7915", 100 | }, 101 | }, 102 | ], 103 | source: { 104 | type: "StringLiteral", 105 | value: "./test-path", 106 | extra: { 107 | rawValue: "./test-path", 108 | raw: '"./test-path"', 109 | }, 110 | start: 130, 111 | end: 143, 112 | }, 113 | } as ExportDeclaration); 114 | }); 115 | 116 | it("should parse the syntax normally", () => { 117 | const result = parser.parse(`cho phép mặc định con mèo`, Declaration); 118 | 119 | expect(toPlainObject(result)).toStrictEqual({ 120 | type: "ExportDefaultDeclaration", 121 | declaration: { 122 | type: "Identifier", 123 | name: "_con_m232o", 124 | }, 125 | } as ExportDeclaration); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/__test__/declaration-import.test.ts: -------------------------------------------------------------------------------- 1 | import parser from "../../../setup-test"; 2 | import toPlainObject from "../../../toPlainObject"; 3 | import { Declaration } from "../Declaration"; 4 | import { ImportDeclaration } from "../import/ImportDeclaration"; 5 | 6 | describe("declaration-import.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse(`sử dụng "./test-path"`, Declaration); 9 | 10 | expect(toPlainObject(result)).toStrictEqual({ 11 | type: "ImportDeclaration", 12 | specifiers: [], 13 | source: { 14 | type: "StringLiteral", 15 | value: "./test-path", 16 | extra: { 17 | rawValue: "./test-path", 18 | raw: '"./test-path"', 19 | }, 20 | start: 8, 21 | end: 21, 22 | }, 23 | assertions: [], 24 | } as ImportDeclaration); 25 | }); 26 | 27 | it("should parse the syntax normally", () => { 28 | const result = parser.parse(`sử dụng * như là abc từ "./test-path"`, Declaration); 29 | 30 | expect(toPlainObject(result)).toStrictEqual({ 31 | type: "ImportDeclaration", 32 | specifiers: [ 33 | { 34 | type: "ImportNamespaceSpecifier", 35 | local: { 36 | type: "Identifier", 37 | name: "abc", 38 | }, 39 | }, 40 | ], 41 | source: { 42 | type: "StringLiteral", 43 | value: "./test-path", 44 | extra: { 45 | rawValue: "./test-path", 46 | raw: '"./test-path"', 47 | }, 48 | start: 24, 49 | end: 37, 50 | }, 51 | assertions: [], 52 | } as ImportDeclaration); 53 | }); 54 | 55 | it("should parse the syntax normally", () => { 56 | const result = parser.parse(`sử dụng cái gì đó từ "./test-path"`, Declaration); 57 | 58 | expect(toPlainObject(result)).toStrictEqual({ 59 | type: "ImportDeclaration", 60 | specifiers: [ 61 | { 62 | type: "ImportDefaultSpecifier", 63 | local: { 64 | type: "Identifier", 65 | name: "_c225i_g236_273243", 66 | }, 67 | }, 68 | ], 69 | source: { 70 | type: "StringLiteral", 71 | value: "./test-path", 72 | extra: { 73 | rawValue: "./test-path", 74 | raw: '"./test-path"', 75 | }, 76 | start: 20, 77 | end: 33, 78 | }, 79 | assertions: [], 80 | } as ImportDeclaration); 81 | }); 82 | 83 | it("should parse the syntax normally", () => { 84 | const result = parser.parse(`sử dụng cái gì đó, { con cún : con chó con } từ "./test-path"`, Declaration); 85 | expect(toPlainObject(result)).toStrictEqual({ 86 | type: "ImportDeclaration", 87 | specifiers: [ 88 | { 89 | local: { 90 | type: "Identifier", 91 | name: "_c225i_g236_273243", 92 | }, 93 | }, 94 | { 95 | type: "ImportSpecifier", 96 | imported: { 97 | type: "Identifier", 98 | name: "_con_c250n", 99 | }, 100 | local: { 101 | type: "Identifier", 102 | name: "_con_ch243_con", 103 | }, 104 | }, 105 | ], 106 | source: { 107 | type: "StringLiteral", 108 | value: "./test-path", 109 | extra: { 110 | rawValue: "./test-path", 111 | raw: '"./test-path"', 112 | }, 113 | start: 48, 114 | end: 61, 115 | }, 116 | assertions: [], 117 | importType: "value", 118 | } as ImportDeclaration); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-class.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Declaration } from "../../Declaration"; 5 | 6 | describe("generator-declaration-class.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = ` 9 | lớp Con Mèo (Động Vật) { 10 | số chân = 4 11 | bất đồng bộ kêu(số lần, hmm) { 12 | trả về "Meo meo" 13 | } 14 | } 15 | `; 16 | 17 | const ast = parser.parse(code, Declaration); 18 | 19 | const result = generate(ast); 20 | 21 | expect(result.code).toBe(`class _Con_M232o extends _2727897ng_V7853t { 22 | _s7889_ch226n = 4; 23 | async _k234u(_s7889_l7847n, _hmm) { 24 | return "Meo meo"; 25 | } 26 | }`); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-variable.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Declaration } from "../../Declaration"; 5 | 6 | describe("generator-expression-array.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = `khai báo số một = 1`; 9 | 10 | const ast = parser.parse(code, Declaration); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toBe(`var _s7889_m7897t = 1;`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/class/ClassBody.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ClassElementList } from "./ClassElementList"; 4 | import { ClassProperty } from "./ClassProperty"; 5 | 6 | export class ClassBody { 7 | type = "ClassBody"; 8 | 9 | body: Array = []; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat("{"); 13 | 14 | this.body = new ClassElementList(parser).properties; 15 | 16 | parser.eat("}"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/class/ClassElementList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ClassProperty } from "./ClassProperty"; 4 | 5 | export class ClassElementList { 6 | type = "ClassElementList"; 7 | 8 | properties: Array = []; 9 | 10 | constructor(parser: Parser) { 11 | while (parser.lookahead?.type !== "}") { 12 | this.properties.push(new ClassProperty(parser)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/class/ClassMethod.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | import { FunctionExpression } from "@parser/nodes/expressions/FunctionExpression"; 3 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 4 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 5 | import { Parser } from "@parser/parser"; 6 | import { Keyword } from "@vietscript/shared"; 7 | 8 | export class ClassMethod { 9 | type = "ClassMethod"; 10 | 11 | static = false; 12 | 13 | key: Identifier | Expression; 14 | 15 | computed = false; 16 | 17 | kind: "method" | "get" | "set" = "method"; 18 | 19 | id = null; 20 | 21 | generator = false; 22 | 23 | async = false; 24 | 25 | params: Identifier[] = []; 26 | 27 | body: BlockStatement; 28 | 29 | constructor(parser: Parser, isStatic = false) { 30 | this.static = isStatic; 31 | switch (parser.lookahead?.type) { 32 | case Keyword.GET: { 33 | parser.eat(Keyword.GET); 34 | this.kind = "get"; 35 | break; 36 | } 37 | case Keyword.SET: { 38 | parser.eat(Keyword.SET); 39 | this.kind = "set"; 40 | break; 41 | } 42 | case Keyword.ASYNC: { 43 | parser.eat(Keyword.ASYNC); 44 | this.async = true; 45 | break; 46 | } 47 | } 48 | 49 | if (parser.lookahead?.type === "[") { 50 | parser.eat("["); 51 | this.key = new Expression(parser); 52 | this.computed = true; 53 | parser.eat("]"); 54 | } else { 55 | this.key = new Identifier(parser); 56 | } 57 | 58 | const _method = new FunctionExpression(parser, true); 59 | 60 | this.id = _method.id; 61 | this.generator = _method.generator; 62 | this.params = _method.params; 63 | this.body = _method.body; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/class/ClassProperty.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Parser } from "@parser/parser"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | import { ClassMethod } from "./ClassMethod"; 7 | 8 | export class ClassProperty { 9 | type = "ClassProperty"; 10 | 11 | static = false; 12 | 13 | computed = false; 14 | 15 | key: Identifier | Expression; 16 | 17 | value: null | Expression; 18 | 19 | constructor(parser: Parser) { 20 | if (parser.lookahead?.type === Keyword.STATIC) { 21 | parser.eat(Keyword.STATIC); 22 | this.static = true; 23 | } 24 | 25 | switch (parser.lookahead?.type) { 26 | case Keyword.GET: 27 | case Keyword.SET: 28 | case Keyword.ASYNC: { 29 | Object.assign(this, new ClassMethod(parser)); 30 | break; 31 | } 32 | default: { 33 | if (parser.lookahead?.type === "[") { 34 | parser.eat("["); 35 | this.key = new Expression(parser); 36 | this.computed = true; 37 | parser.eat("]"); 38 | } else { 39 | this.key = new Identifier(parser); 40 | } 41 | 42 | if (parser.lookahead?.type === "=") { 43 | parser.eat("="); 44 | this.value = new Expression(parser); 45 | } else { 46 | this.value = null; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportAllDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | import { StringLiteral } from "@parser/nodes/literals/StringLiteral"; 4 | 5 | export class ExportAllDeclaration { 6 | type = "ExportAllDeclaration"; 7 | 8 | source: StringLiteral; 9 | 10 | constructor(parser: Parser) { 11 | parser.eat("*"); 12 | 13 | parser.eat(Keyword.FROM); 14 | 15 | this.source = new StringLiteral(parser); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | import { ExportAllDeclaration } from "./ExportAllDeclaration"; 5 | import { ExportNamedDeclaration } from "./ExportNamedDeclaration"; 6 | import { ExportDefaultDeclaration } from "./ExportDefaultDeclaration"; 7 | 8 | export class ExportDeclaration { 9 | constructor(parser: Parser) { 10 | if (parser.lookahead?.type === Keyword.EXPORT) { 11 | parser.eat(Keyword.EXPORT); 12 | } 13 | 14 | switch (parser.lookahead?.type) { 15 | case "*": { 16 | Object.assign(this, new ExportAllDeclaration(parser)); 17 | break; 18 | } 19 | case "{": { 20 | Object.assign(this, new ExportNamedDeclaration(parser)); 21 | break; 22 | } 23 | case Keyword.DEFAULT: { 24 | Object.assign(this, new ExportDefaultDeclaration(parser)); 25 | break; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportDefaultDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | 5 | export class ExportDefaultDeclaration { 6 | type = "ExportDefaultDeclaration"; 7 | 8 | declaration: Expression; 9 | 10 | constructor(parser: Parser) { 11 | parser.eat(Keyword.DEFAULT); 12 | 13 | this.declaration = new Expression(parser); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportNamedDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | import { StringLiteral } from "@parser/nodes/literals/StringLiteral"; 4 | 5 | import { ExportSpecifier } from "./ExportSpecifier"; 6 | import { ExportsList } from "./ExportsList"; 7 | 8 | export class ExportNamedDeclaration { 9 | type = "ExportNamedDeclaration"; 10 | 11 | specifiers: ExportSpecifier[] = []; 12 | 13 | source: StringLiteral; 14 | 15 | constructor(parser: Parser) { 16 | parser.eat("{"); 17 | 18 | this.specifiers.push(...new ExportsList(parser).specifiers); 19 | 20 | parser.eat("}"); 21 | 22 | if (parser.lookahead?.type === Keyword.FROM) { 23 | parser.eat(Keyword.FROM); 24 | 25 | this.source = new StringLiteral(parser); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportSpecifier.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | 4 | export class ExportSpecifier { 5 | type = "ExportSpecifier"; 6 | 7 | local: Identifier; 8 | 9 | exported: Identifier; 10 | 11 | constructor(parser: Parser) { 12 | this.local = new Identifier(parser); 13 | 14 | if (parser.lookahead?.type === ":") { 15 | parser.eat(":"); 16 | this.exported = new Identifier(parser); 17 | } else { 18 | this.exported = this.local; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/export/ExportsList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ExportSpecifier } from "./ExportSpecifier"; 4 | 5 | export class ExportsList { 6 | specifiers: ExportSpecifier[] = []; 7 | 8 | constructor(parser: Parser) { 9 | while (parser.lookahead?.type !== "}") { 10 | this.specifiers.push(new ExportSpecifier(parser)); 11 | 12 | if (parser.lookahead?.type === ",") { 13 | parser.eat(","); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportClause.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { ImportNamespaceSpecifier } from "./ImportNamespaceSpecifier"; 5 | import { ImportDefaultSpecifier } from "./ImportDefaultSpecifier"; 6 | import { ImportSpecifier } from "./ImportSpecifier"; 7 | import { ImportsList } from "./ImportsList"; 8 | 9 | export class ImportClause { 10 | specifiers: Array = []; 11 | 12 | constructor(parser: Parser) { 13 | switch (parser.lookahead?.type) { 14 | case "*": { 15 | this.specifiers = [new ImportNamespaceSpecifier(parser)]; 16 | break; 17 | } 18 | case "{": { 19 | parser.eat("{"); 20 | this.specifiers = new ImportsList(parser).specifiers; 21 | parser.eat("}"); 22 | 23 | // @ts-expect-error 24 | if (parser.lookahead?.type === ",") { 25 | parser.eat(","); 26 | parser.eat("{"); 27 | this.specifiers.push(new ImportDefaultSpecifier(parser)); 28 | parser.eat("}"); 29 | } 30 | break; 31 | } 32 | case Keyword.IDENTIFIER: { 33 | this.specifiers = [new ImportDefaultSpecifier(parser)]; 34 | 35 | // @ts-expect-error 36 | if (parser.lookahead?.type === ",") { 37 | parser.eat(","); 38 | parser.eat("{"); 39 | this.specifiers.push(...new ImportsList(parser).specifiers); 40 | parser.eat("}"); 41 | } 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | import { StringLiteral } from "../../literals/StringLiteral"; 5 | 6 | import { ImportSpecifier } from "./ImportSpecifier"; 7 | import { ImportClause } from "./ImportClause"; 8 | import { ImportDefaultSpecifier } from "./ImportDefaultSpecifier"; 9 | import { ImportNamespaceSpecifier } from "./ImportNamespaceSpecifier"; 10 | 11 | export class ImportDeclaration { 12 | type = "ImportDeclaration"; 13 | 14 | specifiers: Array = []; 15 | 16 | source: StringLiteral; 17 | 18 | assertions = []; 19 | 20 | importType?: "value"; 21 | 22 | constructor(parser: Parser) { 23 | parser.eat(Keyword.IMPORT); 24 | 25 | if ( 26 | parser.lookahead?.type === Keyword.IDENTIFIER || 27 | parser.lookahead?.type === "*" || 28 | parser.lookahead?.type === "{" 29 | ) { 30 | this.importType = "value"; 31 | 32 | this.specifiers = new ImportClause(parser).specifiers; 33 | 34 | parser.eat(Keyword.FROM); 35 | } 36 | 37 | this.source = new StringLiteral(parser); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportDefaultSpecifier.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class ImportDefaultSpecifier { 5 | type: "ImportDefaultSpecifier"; 6 | 7 | local: Identifier; 8 | 9 | constructor(parser: Parser) { 10 | this.local = new Identifier(parser); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportNamespaceSpecifier.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 2 | import { Parser } from "@parser/parser"; 3 | import { Keyword } from "@vietscript/shared"; 4 | 5 | export class ImportNamespaceSpecifier { 6 | type: "ImportNamespaceSpecifier"; 7 | 8 | local: Identifier; 9 | 10 | constructor(parser: Parser) { 11 | parser.eat("*"); 12 | 13 | parser.eat(Keyword.AS); 14 | 15 | this.local = new Identifier(parser); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportSpecifier.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | 4 | export class ImportSpecifier { 5 | type = "ImportSpecifier"; 6 | 7 | imported: Identifier; 8 | 9 | local: Identifier; 10 | 11 | constructor(parser: Parser) { 12 | this.imported = new Identifier(parser); 13 | 14 | if (parser.lookahead?.type === ":") { 15 | parser.eat(":"); 16 | 17 | this.local = new Identifier(parser); 18 | } else { 19 | this.local = this.imported; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/declarations/import/ImportsList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ImportSpecifier } from "./ImportSpecifier"; 4 | 5 | export class ImportsList { 6 | specifiers: Array = []; 7 | 8 | constructor(parser: Parser) { 9 | while (parser.lookahead?.type !== "}") { 10 | this.specifiers.push(new ImportSpecifier(parser)); 11 | 12 | if (parser.lookahead?.type === ",") { 13 | parser.eat(","); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/ArrayExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ArrayLiteral } from "../literals/ArrayLiteral"; 4 | 5 | import { Expression } from "./Expression"; 6 | 7 | export class ArrayExpression { 8 | type = "ArrayExpression"; 9 | 10 | elements: Array; 11 | 12 | constructor(parser: Parser) { 13 | this.elements = new ArrayLiteral(parser).elements; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/AssignmentExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class AssignmentExpression { 7 | type = "AssignmentExpression"; 8 | 9 | left: Identifier | Expression; 10 | 11 | operator: string; 12 | 13 | right: Identifier | Expression; 14 | 15 | constructor(parser: Parser, identifier?: Identifier | Expression) { 16 | this.left = 17 | identifier ?? (parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser)); 18 | 19 | this.operator = String(parser.eat("=").value); 20 | 21 | this.right = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/AwaitExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Expression } from "./Expression"; 5 | 6 | export class AwaitExpression { 7 | type = "AwaitExpression"; 8 | 9 | argument: Expression; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat(Keyword.AWAIT); 13 | 14 | this.argument = new Expression(parser); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/BinaryExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class BinaryExpression { 7 | type = "BinaryExpression"; 8 | 9 | left: Identifier | Expression; 10 | 11 | operator: string; 12 | 13 | right: Identifier | Expression; 14 | 15 | constructor(parser: Parser, identifier?: Identifier) { 16 | this.left = 17 | identifier ?? (parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser)); 18 | 19 | switch (parser.lookahead?.type) { 20 | case "+": 21 | case "-": 22 | case "*": 23 | case "/": 24 | case "%": 25 | case "**": 26 | case "^": 27 | case ">": 28 | case ">>": 29 | case ">>>": 30 | case "<": 31 | case "<<": 32 | case "<<<": 33 | case ">=": 34 | case "<=": 35 | case "==": 36 | case "===": { 37 | this.operator = String(parser.eat(parser.lookahead?.type).value); 38 | break; 39 | } 40 | } 41 | 42 | this.right = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/CallExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | 5 | export class CallExpression { 6 | type = "CallExpression"; 7 | 8 | callee: Identifier | Expression; 9 | 10 | arguments: Array = []; 11 | 12 | optional = false; 13 | 14 | constructor(parser: Parser, identifier: Identifier | Expression) { 15 | this.callee = identifier; 16 | 17 | parser.eat("("); 18 | 19 | while (parser.lookahead?.type !== ")") { 20 | if (this.arguments.length > 0) { 21 | parser.eat(","); 22 | } 23 | 24 | this.arguments.push(new Expression(parser)); 25 | } 26 | 27 | parser.eat(")"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/FunctionExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 3 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class FunctionExpression { 7 | type = "FunctionExpression"; 8 | 9 | id: null; 10 | 11 | expression = false; 12 | 13 | generator = false; 14 | 15 | async = false; 16 | 17 | params: Array = []; 18 | 19 | body: BlockStatement; 20 | 21 | constructor(parser: Parser, ignoreFunctionKeyword = false) { 22 | if (parser.lookahead?.type === Keyword.ASYNC) { 23 | parser.eat(Keyword.ASYNC); 24 | this.async = true; 25 | } 26 | 27 | if (!ignoreFunctionKeyword) { 28 | parser.eat(Keyword.FUNCTION); 29 | } 30 | 31 | if (parser.lookahead?.type === "*") { 32 | parser.eat("*"); 33 | this.generator = true; 34 | } 35 | 36 | parser.eat("("); 37 | 38 | const parameters: Array = []; 39 | 40 | while (parser.lookahead?.type !== ")") { 41 | if (parameters.length > 0) { 42 | parser.eat(","); 43 | } 44 | 45 | parameters.push(new Identifier(parser)); 46 | } 47 | 48 | parser.eat(")"); 49 | 50 | const body = new BlockStatement(parser); 51 | 52 | this.id = null; 53 | this.expression = false; 54 | this.params = parameters; 55 | this.body = body; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/LogicalExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class LogicalExpression { 7 | type = "LogicalExpression"; 8 | 9 | left: Identifier | Expression; 10 | 11 | operator: string; 12 | 13 | right: Identifier | Expression; 14 | 15 | constructor(parser: Parser, identifier?: Identifier) { 16 | this.left = 17 | identifier ?? (parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser)); 18 | 19 | switch (parser.lookahead?.type) { 20 | case "??": 21 | case "||": 22 | case "&&": { 23 | this.operator = String(parser.eat(parser.lookahead?.type).value); 24 | break; 25 | } 26 | } 27 | 28 | this.right = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/MemberExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser"; 2 | 3 | import { Identifier } from "../identifier/Identifier"; 4 | 5 | import { Expression } from "./Expression"; 6 | 7 | export class MemberExpression { 8 | type = "MemberExpression"; 9 | 10 | object: Expression; 11 | 12 | property: Expression | null = null; 13 | 14 | computed = false; 15 | 16 | optional = false; 17 | 18 | constructor(parser: Parser, object: Expression) { 19 | this.object = object; 20 | 21 | do { 22 | switch (parser.lookahead?.type) { 23 | case "[": { 24 | parser.eat("["); 25 | this.object = this.property ? { ...this } : { ...this.object }; 26 | this.property = new Expression(parser); 27 | 28 | parser.eat("]"); 29 | 30 | this.computed = true; 31 | 32 | break; 33 | } 34 | case ".": { 35 | parser.eat("."); 36 | this.object = this.property ? { ...this } : { ...this.object }; 37 | this.property = new Identifier(parser); 38 | 39 | this.computed = false; 40 | 41 | break; 42 | } 43 | } 44 | } while (parser.lookahead?.type === "." || parser.lookahead?.type === "["); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/ObjectExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ObjectLiteral } from "../literals/ObjectLiteral"; 4 | 5 | export class ObjectExpression { 6 | type = "ObjectExpression"; 7 | 8 | properties: ObjectLiteral["properties"] = []; 9 | 10 | constructor(parser: Parser) { 11 | this.properties = new ObjectLiteral(parser).properties; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/PrimaryExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Expression } from "./Expression"; 4 | 5 | // https://262.ecma-international.org/13.0/#prod-PrimaryExpression 6 | export class PrimaryExpression { 7 | constructor(parser: Parser) { 8 | switch (parser.lookahead?.type as string) { 9 | default: { 10 | Object.assign(this, new Expression(parser)); 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/ThisExpression.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class ThisExpression { 5 | type = "ThisExpression"; 6 | 7 | constructor(parser: Parser) { 8 | parser.eat(Keyword.THIS); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/UnaryExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Expression } from "@parser/nodes/expressions/Expression"; 3 | 4 | export class UnaryExpression { 5 | type = "UnaryExpression"; 6 | 7 | operator: string; 8 | 9 | prefix: boolean; 10 | 11 | argument: Expression; 12 | 13 | constructor(parser: Parser) { 14 | switch (parser.lookahead?.type) { 15 | case "delete": 16 | case "void": 17 | case "typeof": 18 | case "+": 19 | case "-": 20 | case "~": 21 | case "!": { 22 | this.operator = String(parser.eat(parser.lookahead?.type).value); 23 | this.prefix = true; 24 | this.argument = new Expression(parser); 25 | break; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/UpdateExpression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Keyword } from "@vietscript/shared"; 4 | 5 | export class UpdateExpression { 6 | type = "UpdateExpression"; 7 | 8 | operator: string; 9 | 10 | argument: Identifier; 11 | 12 | prefix: boolean; 13 | 14 | constructor(parser: Parser) { 15 | if (parser.lookahead?.type === "++" || parser.lookahead?.type === "--") { 16 | this.prefix = true; 17 | this.operator = String(parser.eat(parser.lookahead.type).value); 18 | 19 | this.argument = new Identifier(parser); 20 | } else if (parser.lookahead?.type === Keyword.IDENTIFIER) { 21 | this.argument = new Identifier(parser); 22 | 23 | this.prefix = false; 24 | this.operator = String(parser.eat(parser.lookahead.type).value); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/YieldExpression.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | import { isExpression } from "@parser/utils/is-expression"; 4 | 5 | import { Expression } from "./Expression"; 6 | 7 | export class YieldExpression { 8 | type = "YieldExpression"; 9 | 10 | delegate = false; 11 | 12 | argument: Expression | null = null; 13 | 14 | constructor(parser: Parser) { 15 | parser.eat(Keyword.YIELD); 16 | 17 | if (parser.lookahead?.type === "*") { 18 | this.delegate = true; 19 | 20 | this.argument = new Expression(parser); 21 | } else if (isExpression(parser)) { 22 | this.argument = new Expression(parser); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/expression-array.test.ts: -------------------------------------------------------------------------------- 1 | import parser from "../../../setup-test"; 2 | import toPlainObject from "../../../toPlainObject"; 3 | import { ArrayExpression } from "../ArrayExpression"; 4 | import { Expression } from "../Expression"; 5 | 6 | describe("expression-array.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse(`[1, "a", đúng, sai, "đúng", "true", NaN]`, Expression); 9 | 10 | expect(toPlainObject(result)).toStrictEqual({ 11 | type: "ArrayExpression", 12 | elements: [ 13 | { 14 | type: "NumericLiteral", 15 | value: 1, 16 | extra: { 17 | rawValue: 1, 18 | raw: "1", 19 | }, 20 | start: 1, 21 | end: 2, 22 | }, 23 | { 24 | type: "StringLiteral", 25 | value: "a", 26 | extra: { 27 | rawValue: "a", 28 | raw: '"a"', 29 | }, 30 | start: 4, 31 | end: 7, 32 | }, 33 | { 34 | type: "BooleanLiteral", 35 | value: true, 36 | start: 9, 37 | end: 13, 38 | }, 39 | { 40 | type: "BooleanLiteral", 41 | value: false, 42 | start: 15, 43 | end: 18, 44 | }, 45 | { 46 | type: "StringLiteral", 47 | value: "đúng", 48 | extra: { 49 | rawValue: "đúng", 50 | raw: '"đúng"', 51 | }, 52 | start: 20, 53 | end: 26, 54 | }, 55 | { 56 | type: "StringLiteral", 57 | value: "true", 58 | extra: { 59 | rawValue: "true", 60 | raw: '"true"', 61 | }, 62 | start: 28, 63 | end: 34, 64 | }, 65 | { 66 | type: "Identifier", 67 | name: "NaN", 68 | start: 36, 69 | end: 39, 70 | }, 71 | ], 72 | } as ArrayExpression); 73 | }); 74 | 75 | it("should parse the syntax normally", () => { 76 | const result = parser.parse(`[]`, Expression); 77 | 78 | expect(toPlainObject(result)).toStrictEqual({ 79 | type: "ArrayExpression", 80 | elements: [], 81 | } as ArrayExpression); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/expression-binary.test.ts: -------------------------------------------------------------------------------- 1 | import { BinaryExpression } from "@parser/nodes/expressions/BinaryExpression"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | import { Expression } from "../Expression"; 6 | 7 | describe("expression-binary.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse("xin chào === hello", Expression); 10 | 11 | expect(toPlainObject(result)).toStrictEqual({ 12 | type: "BinaryExpression", 13 | operator: "===", 14 | left: { 15 | type: "Identifier", 16 | name: "_xin_ch224o", 17 | }, 18 | right: { 19 | type: "Identifier", 20 | name: "_hello", 21 | }, 22 | } as BinaryExpression); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/expression-call.test.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | import { CallExpression } from "../CallExpression"; 6 | 7 | describe("expression-call.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse("con chó.kêu()", Expression); 10 | 11 | expect(toPlainObject(result)).toStrictEqual({ 12 | type: "CallExpression", 13 | arguments: [], 14 | optional: false, 15 | callee: { 16 | type: "MemberExpression", 17 | object: { 18 | type: "Identifier", 19 | name: "_con_ch243", 20 | }, 21 | property: { 22 | type: "Identifier", 23 | name: "_k234u", 24 | }, 25 | computed: false, 26 | optional: false, 27 | }, 28 | } as CallExpression); 29 | }); 30 | 31 | it("should parse the syntax normally", () => { 32 | const result = parser.parse("con chó.chân phải.đá()", Expression); 33 | 34 | expect(toPlainObject(result)).toStrictEqual({ 35 | type: "CallExpression", 36 | arguments: [], 37 | optional: false, 38 | callee: { 39 | type: "MemberExpression", 40 | object: { 41 | type: "MemberExpression", 42 | object: { 43 | type: "Identifier", 44 | name: "_con_ch243", 45 | }, 46 | property: { 47 | type: "Identifier", 48 | name: "_ch226n_ph7843i", 49 | }, 50 | computed: false, 51 | optional: false, 52 | }, 53 | property: { 54 | type: "Identifier", 55 | name: "_273225", 56 | }, 57 | computed: false, 58 | optional: false, 59 | }, 60 | } as CallExpression); 61 | }); 62 | 63 | it("should parse the syntax normally", () => { 64 | const result = parser.parse("con chó[chân].đá()", Expression); 65 | 66 | expect(toPlainObject(result)).toStrictEqual({ 67 | type: "CallExpression", 68 | arguments: [], 69 | optional: false, 70 | callee: { 71 | type: "MemberExpression", 72 | object: { 73 | type: "MemberExpression", 74 | object: { 75 | type: "Identifier", 76 | name: "_con_ch243", 77 | }, 78 | property: { 79 | type: "Identifier", 80 | name: "_ch226n", 81 | }, 82 | computed: true, 83 | optional: false, 84 | }, 85 | property: { 86 | type: "Identifier", 87 | name: "_273225", 88 | }, 89 | computed: false, 90 | optional: false, 91 | }, 92 | } as CallExpression); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/expression-member.test.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | import { MemberExpression } from "../MemberExpression"; 6 | import { AssignmentExpression } from "../AssignmentExpression"; 7 | 8 | describe("expression-member.test", () => { 9 | it("should parse the syntax normally", () => { 10 | const result = parser.parse("con chó.chân phải.móng chân.độ dài", Expression); 11 | 12 | expect(toPlainObject(result)).toStrictEqual({ 13 | type: "MemberExpression", 14 | object: { 15 | type: "MemberExpression", 16 | object: { 17 | type: "MemberExpression", 18 | object: { 19 | type: "Identifier", 20 | name: "_con_ch243", 21 | }, 22 | property: { 23 | type: "Identifier", 24 | name: "_ch226n_ph7843i", 25 | }, 26 | computed: false, 27 | optional: false, 28 | }, 29 | property: { 30 | type: "Identifier", 31 | name: "_m243ng_ch226n", 32 | }, 33 | computed: false, 34 | optional: false, 35 | }, 36 | property: { 37 | type: "Identifier", 38 | name: "_2737897_d224i", 39 | }, 40 | computed: false, 41 | optional: false, 42 | } as MemberExpression); 43 | }); 44 | 45 | it("should parse the syntax normally", () => { 46 | const result = parser.parse("con chó[chân phải].móng chân.độ dài", Expression); 47 | 48 | expect(toPlainObject(result)).toStrictEqual({ 49 | type: "MemberExpression", 50 | object: { 51 | type: "MemberExpression", 52 | object: { 53 | type: "MemberExpression", 54 | object: { 55 | type: "Identifier", 56 | name: "_con_ch243", 57 | }, 58 | property: { 59 | type: "Identifier", 60 | name: "_ch226n_ph7843i", 61 | }, 62 | computed: true, 63 | optional: false, 64 | }, 65 | property: { 66 | type: "Identifier", 67 | name: "_m243ng_ch226n", 68 | }, 69 | computed: false, 70 | optional: false, 71 | }, 72 | property: { 73 | type: "Identifier", 74 | name: "_2737897_d224i", 75 | }, 76 | computed: false, 77 | optional: false, 78 | } as MemberExpression); 79 | }); 80 | 81 | it("should parse the syntax normally", () => { 82 | const result = parser.parse("con chó.chân phải.móng chân.độ dài = 123", Expression); 83 | 84 | expect(toPlainObject(result)).toStrictEqual({ 85 | type: "AssignmentExpression", 86 | left: { 87 | type: "MemberExpression", 88 | object: { 89 | type: "MemberExpression", 90 | object: { 91 | type: "MemberExpression", 92 | object: { 93 | type: "Identifier", 94 | name: "_con_ch243", 95 | }, 96 | property: { 97 | type: "Identifier", 98 | name: "_ch226n_ph7843i", 99 | }, 100 | computed: false, 101 | optional: false, 102 | }, 103 | property: { 104 | type: "Identifier", 105 | name: "_m243ng_ch226n", 106 | }, 107 | computed: false, 108 | optional: false, 109 | }, 110 | property: { 111 | type: "Identifier", 112 | name: "_2737897_d224i", 113 | }, 114 | computed: false, 115 | optional: false, 116 | }, 117 | operator: "=", 118 | right: { 119 | type: "NumericLiteral", 120 | value: 123, 121 | extra: { 122 | rawValue: 123, 123 | raw: "123", 124 | }, 125 | start: 37, 126 | end: 40, 127 | }, 128 | } as AssignmentExpression); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/expression-object.test.ts: -------------------------------------------------------------------------------- 1 | import parser from "../../../setup-test"; 2 | import toPlainObject from "../../../toPlainObject"; 3 | import { Expression } from "../Expression"; 4 | import { ObjectExpression } from "../ObjectExpression"; 5 | 6 | describe("expression-object.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse( 9 | ` 10 | { 11 | tiếng kêu: "Meo meo", 12 | số chân: 4, 13 | bất đồng bộ kêu(số lần, hmm) { 14 | trả về "Meo meo" 15 | } 16 | } 17 | `, 18 | Expression, 19 | ); 20 | expect(toPlainObject(result)).toStrictEqual({ 21 | type: "ObjectExpression", 22 | properties: [ 23 | { 24 | type: "ObjectProperty", 25 | method: false, 26 | computed: false, 27 | key: { 28 | type: "Identifier", 29 | name: "_ti7871ng_k234u", 30 | }, 31 | value: { 32 | type: "StringLiteral", 33 | value: "Meo meo", 34 | extra: { 35 | rawValue: "Meo meo", 36 | raw: '"Meo meo"', 37 | }, 38 | start: 22, 39 | end: 31, 40 | }, 41 | }, 42 | { 43 | type: "ObjectProperty", 44 | method: false, 45 | computed: false, 46 | key: { 47 | type: "Identifier", 48 | name: "_s7889_ch226n", 49 | }, 50 | value: { 51 | type: "NumericLiteral", 52 | value: 4, 53 | extra: { 54 | rawValue: 4, 55 | raw: "4", 56 | }, 57 | start: 45, 58 | end: 46, 59 | }, 60 | }, 61 | { 62 | type: "ObjectMethod", 63 | method: true, 64 | key: { 65 | type: "Identifier", 66 | name: "_k234u", 67 | }, 68 | computed: false, 69 | kind: "method", 70 | generator: false, 71 | async: true, 72 | params: [ 73 | { 74 | type: "Identifier", 75 | name: "_s7889_l7847n", 76 | }, 77 | { 78 | type: "Identifier", 79 | name: "_hmm", 80 | }, 81 | ], 82 | body: { 83 | type: "BlockStatement", 84 | body: [ 85 | { 86 | type: "ReturnStatement", 87 | argument: { 88 | type: "StringLiteral", 89 | value: "Meo meo", 90 | extra: { 91 | rawValue: "Meo meo", 92 | raw: '"Meo meo"', 93 | }, 94 | start: 93, 95 | end: 102, 96 | }, 97 | }, 98 | ], 99 | directives: [], 100 | }, 101 | }, 102 | ], 103 | } as ObjectExpression); 104 | }); 105 | 106 | it("should parse the syntax normally", () => { 107 | const result = parser.parse(`{}`, Expression); 108 | 109 | expect(toPlainObject(result)).toStrictEqual({ 110 | type: "ObjectExpression", 111 | properties: [], 112 | } as ObjectExpression); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/generator/generator-expression-array.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Expression } from "../../Expression"; 5 | 6 | describe("generator-expression-array.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = `[1, "a", đúng, sai, "đúng", "true", NaN]`; 9 | 10 | const ast = parser.parse(code, Expression); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toBe(`[1, "a", true, false, "đúng", "true", NaN]`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/generator/generator-expression-binary.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Expression } from "../../Expression"; 5 | 6 | describe("generator-expression-array.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = `xin chào === hello`; 9 | 10 | const ast = parser.parse(code, Expression); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toBe(`_xin_ch224o === _hello`); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/generator/generator-expression-call.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Expression } from "../../Expression"; 5 | 6 | describe("generator-expression-call.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = `con chó.kêu()`; 9 | 10 | const ast = parser.parse(code, Expression); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toBe(`_con_ch243._k234u()`); 15 | }); 16 | 17 | it("should parse the syntax normally", () => { 18 | const code = `con chó.chân phải.đá()`; 19 | 20 | const ast = parser.parse(code, Expression); 21 | 22 | const result = generate(ast); 23 | 24 | expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._273225()`); 25 | }); 26 | 27 | it("should parse the syntax normally", () => { 28 | const code = `con chó[chân].đá()`; 29 | 30 | const ast = parser.parse(code, Expression); 31 | 32 | const result = generate(ast); 33 | 34 | expect(result.code).toBe(`_con_ch243[_ch226n]._273225()`); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/generator/generator-expression-member.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Expression } from "../../Expression"; 5 | 6 | describe("generator-expression-call.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = `con chó.chân phải.móng chân.độ dài`; 9 | 10 | const ast = parser.parse(code, Expression); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._m243ng_ch226n._2737897_d224i`); 15 | }); 16 | 17 | it("should parse the syntax normally", () => { 18 | const code = `con chó[chân phải].móng chân.độ dài`; 19 | 20 | const ast = parser.parse(code, Expression); 21 | 22 | const result = generate(ast); 23 | 24 | expect(result.code).toBe(`_con_ch243[_ch226n_ph7843i]._m243ng_ch226n._2737897_d224i`); 25 | }); 26 | 27 | it("should parse the syntax normally", () => { 28 | const code = `con chó.chân phải.móng chân.độ dài = 123`; 29 | 30 | const ast = parser.parse(code, Expression); 31 | 32 | const result = generate(ast); 33 | 34 | expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._m243ng_ch226n._2737897_d224i = 123`); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object.test.ts: -------------------------------------------------------------------------------- 1 | import generate from "@babel/generator"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import { Expression } from "../../Expression"; 5 | 6 | describe("generator-expression-object.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = ` 9 | { 10 | tiếng kêu: "Meo meo", 11 | số chân: 4, 12 | bất đồng bộ kêu(số lần, hmm) { 13 | trả về "Meo meo" 14 | } 15 | } 16 | `; 17 | const ast = parser.parse(code, Expression); 18 | 19 | const result = generate(ast); 20 | 21 | expect(result.code).toBe(`{ 22 | _ti7871ng_k234u: "Meo meo", 23 | _s7889_ch226n: 4, 24 | async _k234u(_s7889_l7847n, _hmm) { 25 | return "Meo meo"; 26 | } 27 | }`); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/identifier/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class Identifier { 5 | type = "Identifier"; 6 | 7 | name: string; 8 | 9 | constructor(parser: Parser) { 10 | // TODO: optimize later 11 | this.name = `_${String(parser.eat(Keyword.IDENTIFIER)?.value).replace( 12 | /(\s)|(^[0-9]+)|([^\sA-Za-z])/g, 13 | (_, p1, p2, p3) => { 14 | if (p1) return "_"; 15 | else if (p2) return "_" + p2; 16 | else return String(p3.codePointAt(0)); 17 | }, 18 | )}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/identifier/__test__/identifier.test.ts: -------------------------------------------------------------------------------- 1 | import { ObjectExpression } from "@parser/nodes/expressions/ObjectExpression"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | 6 | describe("identifier.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse( 9 | ` 10 | { 11 | // Object này có key siêu dài và câu này cũng có khá đa dạng về ký tự để test việc parse identifier 12 | Trăm năm trong cõi người ta chữ tài chữ mệnh khéo là ghét nhau Trải qua một cuộc bể dâu những điều trông thấy mà đau đớn lòng Lạ gì bỉ sắc tư phong trời xanh quen với má hồng đánh ghen: "Giá trị pha ke" 13 | } 14 | `, 15 | ObjectExpression, 16 | ); 17 | 18 | expect(toPlainObject(result)).toStrictEqual({ 19 | type: "ObjectExpression", 20 | properties: [ 21 | { 22 | type: "ObjectProperty", 23 | method: false, 24 | computed: false, 25 | key: { 26 | type: "Identifier", 27 | name: "_Tr259m_n259m_trong_c245i_ng4327901i_ta_ch7919_t224i_ch7919_m7879nh_kh233o_l224_gh233t_nhau_Tr7843i_qua_m7897t_cu7897c_b7875_d226u_nh7919ng_273i7873u_tr244ng_th7845y_m224_273au_2737899n_l242ng_L7841_g236_b7881_s7855c_t432_phong_tr7901i_xanh_quen_v7899i_m225_h7891ng_273225nh_ghen", 28 | }, 29 | value: { 30 | type: "StringLiteral", 31 | value: "Giá trị pha ke", 32 | extra: { 33 | rawValue: "Giá trị pha ke", 34 | raw: '"Giá trị pha ke"', 35 | }, 36 | start: 305, 37 | end: 321, 38 | }, 39 | }, 40 | ], 41 | } as ObjectExpression); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/initializers/array/ArrayElementList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Expression } from "../../expressions/Expression"; 4 | 5 | export class ArrayElementList { 6 | type = "ArrayElementList"; 7 | 8 | elements: Array = []; 9 | 10 | constructor(parser: Parser, stopToken = "]") { 11 | while (parser.lookahead?.type !== stopToken) { 12 | this.elements.push(new Expression(parser)); 13 | 14 | if (parser.lookahead?.type !== stopToken) { 15 | parser.eat(","); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/initializers/object/ObjectMethod.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | import { FunctionExpression } from "@parser/nodes/expressions/FunctionExpression"; 3 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 4 | import { Parser } from "@parser/parser"; 5 | import { Keyword } from "@vietscript/shared"; 6 | 7 | export class ObjectMethod { 8 | type = "ObjectMethod"; 9 | 10 | method = true; 11 | 12 | key: Identifier | Expression; 13 | 14 | computed = false; 15 | 16 | kind: "method" | "get" | "set" = "method"; 17 | 18 | generator = false; 19 | 20 | async = false; 21 | 22 | params: FunctionExpression["params"] = []; 23 | 24 | body: FunctionExpression["body"]; 25 | 26 | constructor(parser: Parser) { 27 | switch (parser.lookahead?.type) { 28 | case Keyword.GET: { 29 | parser.eat(Keyword.GET); 30 | this.kind = "get"; 31 | this.method = false; 32 | break; 33 | } 34 | case Keyword.SET: { 35 | parser.eat(Keyword.SET); 36 | this.kind = "set"; 37 | this.method = false; 38 | break; 39 | } 40 | case Keyword.ASYNC: { 41 | parser.eat(Keyword.ASYNC); 42 | this.async = true; 43 | break; 44 | } 45 | } 46 | 47 | if (parser.lookahead?.type === "[") { 48 | parser.eat("["); 49 | this.key = new Expression(parser); 50 | this.computed = true; 51 | parser.eat("]"); 52 | } else { 53 | this.key = new Identifier(parser); 54 | } 55 | 56 | const functionExpression = new FunctionExpression(parser, true); 57 | 58 | this.generator = functionExpression.generator; 59 | this.params = functionExpression.params; 60 | this.body = functionExpression.body; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/initializers/object/ObjectProperty.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "@parser/nodes/expressions/Expression"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Parser } from "@parser/parser"; 4 | 5 | export class ObjectProperty { 6 | type = "ObjectProperty"; 7 | 8 | method = false; 9 | 10 | computed = false; 11 | 12 | key: Identifier | Expression; 13 | 14 | value: null | Expression; 15 | 16 | constructor(parser: Parser) { 17 | if (parser.lookahead?.type === "[") { 18 | parser.eat("["); 19 | this.key = new Expression(parser); 20 | this.computed = true; 21 | parser.eat("]"); 22 | } else { 23 | this.key = new Identifier(parser); 24 | } 25 | 26 | if (parser.lookahead?.type === ":") { 27 | parser.eat(":"); 28 | this.value = new Expression(parser); 29 | } else { 30 | this.value = null; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/initializers/object/ObjectPropertyList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { ObjectProperty } from "./ObjectProperty"; 5 | import { ObjectMethod } from "./ObjectMethod"; 6 | 7 | export class ObjectPropertyList { 8 | properties: Array = []; 9 | 10 | constructor(parser: Parser, stopToken = "}") { 11 | while (parser.lookahead?.type !== stopToken) { 12 | switch (parser.lookahead?.type) { 13 | case Keyword.GET: 14 | case Keyword.SET: 15 | case Keyword.ASYNC: { 16 | this.properties.push(new ObjectMethod(parser)); 17 | break; 18 | } 19 | default: { 20 | this.properties.push(new ObjectProperty(parser)); 21 | } 22 | } 23 | 24 | if (parser.lookahead?.type !== stopToken) { 25 | parser.eat(","); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/ArrayLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Expression } from "../expressions/Expression"; 4 | import { ArrayElementList } from "../initializers/array/ArrayElementList"; 5 | 6 | export class ArrayLiteral { 7 | type = "ArrayLiteral"; 8 | 9 | elements: Array; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat("["); 13 | 14 | this.elements = new ArrayElementList(parser, "]").elements; 15 | 16 | parser.eat("]"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/BooleanLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword, Node } from "@vietscript/shared"; 3 | 4 | export class BooleanLiteral implements Node { 5 | type = "BooleanLiteral"; 6 | 7 | value: boolean; 8 | 9 | start: number; 10 | 11 | end: number; 12 | 13 | constructor(parser: Parser) { 14 | const token = parser.eat(Keyword.BOOLEAN); 15 | 16 | this.start = token.start; 17 | this.end = token.end; 18 | 19 | const value = token.value === "true" ? true : token.value === "đúng" ? true : false; 20 | 21 | this.value = value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/Literal.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { BooleanLiteral } from "./BooleanLiteral"; 5 | import { NaNIdentifier } from "./NaNIdentifier"; 6 | import { NullLiteral } from "./NullLiteral"; 7 | import { NumericLiteral } from "./NumericLiteral"; 8 | import { StringLiteral } from "./StringLiteral"; 9 | import { UndefinedIdentifier } from "./UndefinedIdentifier"; 10 | 11 | export class Literal { 12 | constructor(parser: Parser) { 13 | switch (parser.lookahead?.type) { 14 | case Keyword.NUMBER: { 15 | Object.assign(this, new NumericLiteral(parser)); 16 | break; 17 | } 18 | case Keyword.STRING: { 19 | Object.assign(this, new StringLiteral(parser)); 20 | break; 21 | } 22 | case Keyword.BOOLEAN: { 23 | Object.assign(this, new BooleanLiteral(parser)); 24 | break; 25 | } 26 | case Keyword.NULL: { 27 | Object.assign(this, new NullLiteral(parser)); 28 | break; 29 | } 30 | case Keyword.NAN: { 31 | Object.assign(this, new NaNIdentifier(parser)); 32 | break; 33 | } 34 | case Keyword.UNDEFINED: { 35 | Object.assign(this, new UndefinedIdentifier(parser)); 36 | break; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/NaNIdentifier.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword, Node } from "@vietscript/shared"; 3 | 4 | export class NaNIdentifier implements Node { 5 | type = Keyword.IDENTIFIER; 6 | 7 | name = Keyword.NAN; 8 | 9 | start: number; 10 | 11 | end: number; 12 | 13 | constructor(parser: Parser) { 14 | const token = parser.eat(Keyword.NAN); 15 | 16 | this.start = token.start; 17 | this.end = token.end; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/NullLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Keyword, Node } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class NullLiteral implements Node { 5 | type = "NullLiteral"; 6 | 7 | start: number; 8 | 9 | end: number; 10 | 11 | constructor(parser: Parser) { 12 | const token = parser.eat(Keyword.NULL); 13 | 14 | this.start = token.start; 15 | this.end = token.end; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/NumericLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword, Node } from "@vietscript/shared"; 3 | 4 | export class NumericLiteral implements Node { 5 | type = "NumericLiteral"; 6 | 7 | value: number; 8 | 9 | extra: { 10 | rawValue: number; 11 | raw: string; 12 | }; 13 | 14 | start: number; 15 | 16 | end: number; 17 | 18 | constructor(parser: Parser) { 19 | const token = parser.eat(Keyword.NUMBER); 20 | 21 | this.start = token.start; 22 | this.end = token.end; 23 | 24 | this.value = Number(token.value); 25 | 26 | this.extra = { 27 | rawValue: this.value, 28 | raw: String(token.value), 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/ObjectLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { ObjectPropertyList } from "../initializers/object/ObjectPropertyList"; 4 | 5 | export class ObjectLiteral { 6 | type = "ObjectLiteral"; 7 | 8 | properties: ObjectPropertyList["properties"] = []; 9 | 10 | constructor(parser: Parser) { 11 | parser.eat("{"); 12 | 13 | this.properties = new ObjectPropertyList(parser).properties; 14 | 15 | parser.eat("}"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/StringLiteral.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword, Node } from "@vietscript/shared"; 3 | 4 | export class StringLiteral implements Node { 5 | type = "StringLiteral"; 6 | 7 | value: string; 8 | 9 | extra: { 10 | rawValue: string; 11 | raw: string; 12 | }; 13 | 14 | start: number; 15 | 16 | end: number; 17 | 18 | constructor(parser: Parser) { 19 | const token = parser.eat(Keyword.STRING); 20 | 21 | this.start = token.start; 22 | this.end = token.end; 23 | 24 | const value = String(token.value).slice(1, -1); 25 | 26 | this.value = value; 27 | 28 | this.extra = { 29 | rawValue: value, 30 | raw: JSON.stringify(value), 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/UndefinedIdentifier.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword, Node } from "@vietscript/shared"; 3 | 4 | export class UndefinedIdentifier implements Node { 5 | type = Keyword.IDENTIFIER; 6 | 7 | name = Keyword.UNDEFINED; 8 | 9 | start: number; 10 | 11 | end: number; 12 | 13 | constructor(parser: Parser) { 14 | const token = parser.eat(Keyword.UNDEFINED); 15 | 16 | this.start = token.start; 17 | this.end = token.end; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-boolean.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`generator-literal-boolean.test > should parse the syntax normally 1`] = `"true"`; 4 | 5 | exports[`generator-literal-boolean.test > should parse the syntax normally 2`] = `"false"`; 6 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-numeric.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`generator-literal-numeric.test > should parse the syntax normally 1`] = `"0"`; 4 | 5 | exports[`generator-literal-numeric.test > should parse the syntax normally 2`] = `"0.001123"`; 6 | 7 | exports[`generator-literal-numeric.test > should parse the syntax normally 3`] = `"70E-3"`; 8 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-string.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`generator-literal-string.test > should generate the javascript from the syntax normally 1`] = `"\\"Chào thế giới!\\""`; 4 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/generator-literal-boolean.test.ts: -------------------------------------------------------------------------------- 1 | import { Literal } from "@parser/nodes/literals/Literal"; 2 | import generate from "@babel/generator"; 3 | 4 | import parser from "../../../../setup-test"; 5 | 6 | describe("generator-literal-boolean.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = "đúng"; 9 | 10 | const ast = parser.parse(code, Literal); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toMatchSnapshot(); 15 | }); 16 | 17 | it("should parse the syntax normally", () => { 18 | const code = "sai"; 19 | 20 | const ast = parser.parse(code, Literal); 21 | 22 | const result = generate(ast); 23 | 24 | expect(result.code).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/generator-literal-numeric.test.ts: -------------------------------------------------------------------------------- 1 | import { Literal } from "@parser/nodes/literals/Literal"; 2 | import generate from "@babel/generator"; 3 | 4 | import parser from "../../../../setup-test"; 5 | 6 | describe("generator-literal-numeric.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const code = "0"; 9 | 10 | const ast = parser.parse(code, Literal); 11 | 12 | const result = generate(ast); 13 | 14 | expect(result.code).toMatchSnapshot(); 15 | }); 16 | 17 | it("should parse the syntax normally", () => { 18 | const code = "0.001123"; 19 | 20 | const ast = parser.parse(code, Literal); 21 | 22 | const result = generate(ast); 23 | 24 | expect(result.code).toMatchSnapshot(); 25 | }); 26 | 27 | it("should parse the syntax normally", () => { 28 | const code = "70E-3"; 29 | 30 | const ast = parser.parse(code, Literal); 31 | 32 | const result = generate(ast); 33 | 34 | expect(result.code).toMatchSnapshot(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/generator/generator-literal-string.test.ts: -------------------------------------------------------------------------------- 1 | import { Literal } from "@parser/nodes/literals/Literal"; 2 | import generate from "@babel/generator"; 3 | 4 | import parser from "../../../../setup-test"; 5 | 6 | describe("generator-literal-string.test", () => { 7 | it("should generate the javascript from the syntax normally", () => { 8 | const code = `"Chào thế giới!"`; 9 | const ast = parser.parse(code, Literal); 10 | 11 | const result = generate(ast); 12 | 13 | expect(result.code).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/literal-boolean.test.ts: -------------------------------------------------------------------------------- 1 | import { BooleanLiteral } from "@parser/nodes/literals/BooleanLiteral"; 2 | import { Literal } from "@parser/nodes/literals/Literal"; 3 | 4 | import parser from "../../../setup-test"; 5 | import toPlainObject from "../../../toPlainObject"; 6 | 7 | describe("literal-boolean.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse("đúng", Literal); 10 | 11 | expect(toPlainObject(result)).toStrictEqual({ 12 | type: "BooleanLiteral", 13 | value: true, 14 | start: 0, 15 | end: 4, 16 | } as BooleanLiteral); 17 | }); 18 | 19 | it("should parse the syntax normally", () => { 20 | const result = parser.parse("sai", Literal); 21 | 22 | expect(toPlainObject(result)).toStrictEqual({ 23 | type: "BooleanLiteral", 24 | value: false, 25 | start: 0, 26 | end: 3, 27 | } as BooleanLiteral); 28 | }); 29 | 30 | it("should parse the syntax normally", () => { 31 | const result = parser.parse("true", Literal); 32 | 33 | expect(toPlainObject(result)).toStrictEqual({ 34 | type: "BooleanLiteral", 35 | value: true, 36 | start: 0, 37 | end: 4, 38 | } as BooleanLiteral); 39 | }); 40 | 41 | it("should parse the syntax normally", () => { 42 | const result = parser.parse("false", Literal); 43 | 44 | expect(toPlainObject(result)).toStrictEqual({ 45 | type: "BooleanLiteral", 46 | value: false, 47 | start: 0, 48 | end: 5, 49 | } as BooleanLiteral); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/literal-numeric.test.ts: -------------------------------------------------------------------------------- 1 | import { NumericLiteral } from "@parser/nodes/literals/NumericLiteral"; 2 | import { Literal } from "@parser/nodes/literals/Literal"; 3 | 4 | import parser from "../../../setup-test"; 5 | import toPlainObject from "../../../toPlainObject"; 6 | 7 | describe("literal-numeric.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse("0", Literal); 10 | 11 | expect(toPlainObject(result)).toStrictEqual({ 12 | type: "NumericLiteral", 13 | value: 0, 14 | extra: { 15 | rawValue: 0, 16 | raw: "0", 17 | }, 18 | start: 0, 19 | end: 1, 20 | } as NumericLiteral); 21 | }); 22 | 23 | it("should parse the syntax normally", () => { 24 | const result = parser.parse("3.", Literal); 25 | 26 | expect(toPlainObject(result)).toStrictEqual({ 27 | type: "NumericLiteral", 28 | value: 3, 29 | extra: { 30 | rawValue: 3, 31 | raw: "3.", 32 | }, 33 | start: 0, 34 | end: 2, 35 | } as NumericLiteral); 36 | }); 37 | 38 | it("should parse the syntax normally", () => { 39 | const result = parser.parse("0.1", Literal); 40 | 41 | expect(toPlainObject(result)).toStrictEqual({ 42 | type: "NumericLiteral", 43 | value: 0.1, 44 | extra: { 45 | rawValue: 0.1, 46 | raw: "0.1", 47 | }, 48 | start: 0, 49 | end: 3, 50 | } as NumericLiteral); 51 | }); 52 | 53 | it("should parse the syntax normally", () => { 54 | const result = parser.parse("0.001123", Literal); 55 | 56 | expect(toPlainObject(result)).toStrictEqual({ 57 | type: "NumericLiteral", 58 | value: 0.001_123, 59 | extra: { 60 | rawValue: 0.001_123, 61 | raw: "0.001123", 62 | }, 63 | start: 0, 64 | end: 8, 65 | } as NumericLiteral); 66 | }); 67 | 68 | it("should parse the syntax normally", () => { 69 | const result = parser.parse("70e3", Literal); 70 | 71 | expect(toPlainObject(result)).toStrictEqual({ 72 | type: "NumericLiteral", 73 | value: 70e3, 74 | extra: { 75 | rawValue: 70e3, 76 | raw: "70e3", 77 | }, 78 | start: 0, 79 | end: 4, 80 | } as NumericLiteral); 81 | }); 82 | 83 | it("should parse the syntax normally", () => { 84 | const result = parser.parse("70E3", Literal); 85 | 86 | expect(toPlainObject(result)).toStrictEqual({ 87 | type: "NumericLiteral", 88 | value: 70e3, 89 | extra: { 90 | rawValue: 70e3, 91 | raw: "70E3", 92 | }, 93 | start: 0, 94 | end: 4, 95 | } as NumericLiteral); 96 | }); 97 | 98 | it("should parse the syntax normally", () => { 99 | const result = parser.parse("70E+3", Literal); 100 | 101 | expect(toPlainObject(result)).toStrictEqual({ 102 | type: "NumericLiteral", 103 | value: 70e3, 104 | extra: { 105 | rawValue: 70e3, 106 | raw: "70E+3", 107 | }, 108 | start: 0, 109 | end: 5, 110 | } as NumericLiteral); 111 | }); 112 | 113 | it("should parse the syntax normally", () => { 114 | const result = parser.parse("70e-3", Literal); 115 | 116 | expect(toPlainObject(result)).toStrictEqual({ 117 | type: "NumericLiteral", 118 | value: 70e-3, 119 | extra: { 120 | rawValue: 70e-3, 121 | raw: "70e-3", 122 | }, 123 | start: 0, 124 | end: 5, 125 | } as NumericLiteral); 126 | }); 127 | 128 | it("should parse the syntax normally", () => { 129 | const result = parser.parse("70E-3", Literal); 130 | 131 | expect(toPlainObject(result)).toStrictEqual({ 132 | type: "NumericLiteral", 133 | value: 70e-3, 134 | extra: { 135 | rawValue: 70e-3, 136 | raw: "70E-3", 137 | }, 138 | start: 0, 139 | end: 5, 140 | } as NumericLiteral); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/literals/__test__/literal-string.test.ts: -------------------------------------------------------------------------------- 1 | import { StringLiteral } from "@parser/nodes/literals/StringLiteral"; 2 | import { Literal } from "@parser/nodes/literals/Literal"; 3 | 4 | import parser from "../../../setup-test"; 5 | import toPlainObject from "../../../toPlainObject"; 6 | 7 | describe("literal-string.test", () => { 8 | it("should parse the syntax normally", () => { 9 | const result = parser.parse(`"Chào thế giới!"`, Literal); 10 | 11 | expect(toPlainObject(result)).toStrictEqual({ 12 | type: "StringLiteral", 13 | value: "Chào thế giới!", 14 | extra: { 15 | rawValue: "Chào thế giới!", 16 | raw: '"Chào thế giới!"', 17 | }, 18 | start: 0, 19 | end: 16, 20 | } as StringLiteral); 21 | }); 22 | 23 | it("should parse the syntax normally", () => { 24 | const result = parser.parse(`'Chào thế giới!'`, Literal); 25 | 26 | expect(toPlainObject(result)).toStrictEqual({ 27 | type: "StringLiteral", 28 | value: "Chào thế giới!", 29 | extra: { 30 | rawValue: "Chào thế giới!", 31 | raw: '"Chào thế giới!"', 32 | }, 33 | start: 0, 34 | end: 16, 35 | } as StringLiteral); 36 | }); 37 | 38 | it("should parse the syntax normally", () => { 39 | const result = parser.parse("`Chào thế giới!`", Literal); 40 | 41 | expect(toPlainObject(result)).toStrictEqual({ 42 | type: "StringLiteral", 43 | value: "Chào thế giới!", 44 | extra: { 45 | rawValue: "Chào thế giới!", 46 | raw: '"Chào thế giới!"', 47 | }, 48 | start: 0, 49 | end: 16, 50 | } as StringLiteral); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/BlockStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { StatementList } from "./StatementList"; 4 | import { StatementListItem } from "./StatementListItem"; 5 | 6 | export class BlockStatement { 7 | type = "BlockStatement"; 8 | 9 | body: Array; 10 | 11 | directives: Array = []; 12 | 13 | constructor(parser: Parser) { 14 | const statements: Array = []; 15 | 16 | parser.eat("{"); 17 | 18 | // List of statement 19 | statements.push(...new StatementList(parser, ["}"]).body); 20 | 21 | parser.eat("}"); 22 | 23 | this.body = statements; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/BreakStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Identifier } from "../identifier/Identifier"; 5 | 6 | export class BreakStatement { 7 | type = "BreakStatement"; 8 | 9 | label: null | string; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat(Keyword.BREAK); 13 | 14 | let label: BreakStatement["label"] = null; 15 | 16 | if (parser.lookahead?.type === Keyword.IDENTIFIER) { 17 | label = new Identifier(parser).name; 18 | } 19 | 20 | this.label = label; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/ContinueStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Identifier } from "../identifier/Identifier"; 5 | 6 | export class ContinueStatement { 7 | type = "ContinueStatement"; 8 | 9 | label: null | string; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat(Keyword.BREAK); 13 | 14 | let label: ContinueStatement["label"] = null; 15 | 16 | if (parser.lookahead?.type === Keyword.IDENTIFIER) { 17 | label = new Identifier(parser).name; 18 | } 19 | 20 | this.label = label; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/DebuggerStatement.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class DebuggerStatement { 5 | type = "DebuggerStatement"; 6 | 7 | constructor(parser: Parser) { 8 | parser.eat(Keyword.DEBUGGER); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/EmptyStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | export class EmptyStatement { 4 | type = "EmptyStatement"; 5 | 6 | constructor(parser: Parser) { 7 | parser.eat(";"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/ExpressionStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Expression } from "../expressions/Expression"; 4 | 5 | export class ExpressionStatement { 6 | type = "ExpressionStatement"; 7 | 8 | expression: Expression; 9 | 10 | constructor(parser: Parser) { 11 | this.expression = new Expression(parser); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/IfStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | import { BlockStatement } from "./BlockStatement"; 7 | import { Statement } from "./Statement"; 8 | 9 | export class IfStatement { 10 | type = "IfStatement"; 11 | 12 | test: Identifier | Expression; 13 | 14 | consequent: Statement | BlockStatement; 15 | 16 | alternate?: Statement | BlockStatement; 17 | 18 | constructor(parser: Parser) { 19 | parser.eat(Keyword.IF); 20 | 21 | parser.eat("("); 22 | 23 | this.test = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser); 24 | 25 | parser.eat(")"); 26 | 27 | this.consequent = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser); 28 | 29 | if (parser.lookahead?.type === Keyword.ELSE) { 30 | parser.eat(Keyword.ELSE); 31 | 32 | // @ts-ignore 33 | if (parser.lookahead?.type === Keyword.IF) { 34 | this.alternate = new IfStatement(parser); 35 | } else { 36 | this.alternate = 37 | // @ts-ignore 38 | parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/LabelledStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { Identifier } from "../identifier/Identifier"; 4 | 5 | import { IterationStatement } from "./breakable/iteration/IterationStatement"; 6 | 7 | export class LabelledStatement { 8 | type = "LabelledStatement"; 9 | 10 | label: Identifier; 11 | 12 | body: IterationStatement; 13 | 14 | constructor(parser: Parser, identifier: Identifier) { 15 | this.label = identifier; 16 | 17 | parser.eat(":"); 18 | 19 | this.body = new IterationStatement(parser); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/ReturnStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Expression } from "../expressions/Expression"; 5 | 6 | export class ReturnStatement { 7 | type = "ReturnStatement"; 8 | 9 | argument: Expression; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat(Keyword.RETURN); 13 | 14 | const expression = new Expression(parser); 15 | 16 | this.argument = expression; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/Statement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { BlockStatement } from "./BlockStatement"; 5 | import { BreakStatement } from "./BreakStatement"; 6 | import { ContinueStatement } from "./ContinueStatement"; 7 | import { ReturnStatement } from "./ReturnStatement"; 8 | import { ThrowStatement } from "./ThrowStatement"; 9 | import { DebuggerStatement } from "./DebuggerStatement"; 10 | import { BreakableStatement } from "./breakable/BreakableStatement"; 11 | import { IfStatement } from "./IfStatement"; 12 | import { TryStatement } from "./try/TryStatement"; 13 | import { WithStatement } from "./WithStatement"; 14 | import { ExpressionStatement } from "./ExpressionStatement"; 15 | 16 | export class Statement { 17 | [key: string]: any; 18 | 19 | constructor(parser: Parser) { 20 | switch (parser.lookahead?.type) { 21 | case "{": { 22 | Object.assign(this, new BlockStatement(parser)); 23 | break; 24 | } 25 | case Keyword.IF: { 26 | Object.assign(this, new IfStatement(parser)); 27 | break; 28 | } 29 | case Keyword.DO: 30 | case Keyword.WHILE: 31 | case Keyword.FOR: 32 | case Keyword.SWITCH: { 33 | Object.assign(this, new BreakableStatement(parser)); 34 | break; 35 | } 36 | case Keyword.CONTINUE: { 37 | Object.assign(this, new ContinueStatement(parser)); 38 | break; 39 | } 40 | case Keyword.BREAK: { 41 | Object.assign(this, new BreakStatement(parser)); 42 | break; 43 | } 44 | case Keyword.RETURN: { 45 | Object.assign(this, new ReturnStatement(parser)); 46 | break; 47 | } 48 | case Keyword.WITH: { 49 | Object.assign(this, new WithStatement(parser)); 50 | break; 51 | } 52 | case Keyword.THROW: { 53 | Object.assign(this, new ThrowStatement(parser)); 54 | break; 55 | } 56 | case Keyword.TRY: { 57 | Object.assign(this, new TryStatement(parser)); 58 | break; 59 | } 60 | case Keyword.DEBUGGER: { 61 | Object.assign(this, new DebuggerStatement(parser)); 62 | break; 63 | } 64 | default: { 65 | Object.assign(this, new ExpressionStatement(parser)); 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/StatementList.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | 3 | import { EmptyStatement } from "./EmptyStatement"; 4 | import { StatementListItem } from "./StatementListItem"; 5 | 6 | export class StatementList { 7 | body: Array; 8 | 9 | constructor(parser: Parser, stopTokens?: Array) { 10 | const statements: Array = []; 11 | 12 | while ( 13 | parser.tokenizer.isEOF() === false && 14 | !stopTokens?.includes(String(parser.lookahead?.type)) 15 | ) { 16 | const statement = new StatementListItem(parser); 17 | 18 | if (statement !== undefined) { 19 | statements.push(statement); 20 | } 21 | 22 | // Eat the first ";" after every statement 23 | if (parser.lookahead?.type === ";") { 24 | parser.eat(";"); 25 | } 26 | 27 | // The rest would be empty statements 28 | while (parser.lookahead?.type === ";") { 29 | statements.push(new EmptyStatement(parser)); 30 | } 31 | } 32 | 33 | this.body = statements; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/StatementListItem.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Declaration } from "../declarations/Declaration"; 5 | 6 | import { Statement } from "./Statement"; 7 | 8 | export class StatementListItem { 9 | [key: string]: any; 10 | 11 | constructor(parser: Parser) { 12 | switch (parser.lookahead?.type) { 13 | case Keyword.VAR: 14 | case Keyword.LET: 15 | case Keyword.CONST: 16 | case Keyword.ASYNC: 17 | case Keyword.FUNCTION: 18 | case Keyword.CLASS: { 19 | Object.assign(this, new Declaration(parser)); 20 | break; 21 | } 22 | case Keyword.IF: 23 | case Keyword.DO: 24 | case Keyword.WHILE: 25 | case Keyword.FOR: 26 | case Keyword.SWITCH: 27 | case Keyword.CONTINUE: 28 | case Keyword.BREAK: 29 | case Keyword.RETURN: 30 | case Keyword.WITH: 31 | case Keyword.IDENTIFIER: 32 | case Keyword.THROW: 33 | case Keyword.TRY: 34 | case Keyword.DEBUGGER: { 35 | Object.assign(this, new Statement(parser)); 36 | break; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/ThrowStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { Expression } from "../expressions/Expression"; 5 | 6 | export class ThrowStatement { 7 | type = "ThrowStatement"; 8 | 9 | argument: Expression; 10 | 11 | constructor(parser: Parser) { 12 | parser.eat(Keyword.THROW); 13 | 14 | const expression = new Expression(parser); 15 | 16 | this.argument = expression; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/WithStatement.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "@vietscript/shared"; 2 | import { Parser } from "@parser/parser"; 3 | 4 | export class WithStatement { 5 | type = "WithStatement"; 6 | 7 | constructor(parser: Parser) { 8 | parser.eat(Keyword.WITH); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/__test__/statement-if.test.ts: -------------------------------------------------------------------------------- 1 | import { IfStatement } from "@parser/nodes/statements/IfStatement"; 2 | 3 | import parser from "../../../setup-test"; 4 | import toPlainObject from "../../../toPlainObject"; 5 | 6 | describe("statement-if.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse( 9 | ` 10 | nếu (điều kiện một) { 11 | khai báo gì đó; 12 | } không thì nếu (điều kiện hai) { 13 | khai báo gì đó khác; 14 | } không thì { 15 | khai báo gì đó khác nữa; // =)) 16 | } 17 | `, 18 | IfStatement, 19 | ); 20 | 21 | expect(toPlainObject(result)).toStrictEqual({ 22 | type: "IfStatement", 23 | test: { 24 | type: "Identifier", 25 | name: "_273i7873u_ki7879n_m7897t", 26 | }, 27 | consequent: { 28 | type: "BlockStatement", 29 | body: [ 30 | { 31 | type: "VariableDeclaration", 32 | declarations: [ 33 | { 34 | type: "VariableDeclarator", 35 | id: { 36 | type: "Identifier", 37 | name: "_g236_273243", 38 | }, 39 | init: { 40 | type: "Identifier", 41 | name: "undefined", 42 | }, 43 | }, 44 | ], 45 | kind: "var", 46 | }, 47 | ], 48 | directives: [], 49 | }, 50 | alternate: { 51 | type: "IfStatement", 52 | test: { 53 | type: "Identifier", 54 | name: "_273i7873u_ki7879n_hai", 55 | }, 56 | consequent: { 57 | type: "BlockStatement", 58 | body: [ 59 | { 60 | type: "VariableDeclaration", 61 | declarations: [ 62 | { 63 | type: "VariableDeclarator", 64 | id: { 65 | type: "Identifier", 66 | name: "_g236_273243_kh225c", 67 | }, 68 | init: { 69 | type: "Identifier", 70 | name: "undefined", 71 | }, 72 | }, 73 | ], 74 | kind: "var", 75 | }, 76 | ], 77 | directives: [], 78 | }, 79 | alternate: { 80 | type: "BlockStatement", 81 | body: [ 82 | { 83 | type: "VariableDeclaration", 84 | declarations: [ 85 | { 86 | type: "VariableDeclarator", 87 | id: { 88 | type: "Identifier", 89 | name: "_g236_273243_kh225c_n7919a", 90 | }, 91 | init: { 92 | type: "Identifier", 93 | name: "undefined", 94 | }, 95 | }, 96 | ], 97 | kind: "var", 98 | }, 99 | ], 100 | directives: [], 101 | }, 102 | }, 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/BreakableStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { SwitchStatement } from "./SwitchStatement"; 5 | import { IterationStatement } from "./iteration/IterationStatement"; 6 | 7 | export class BreakableStatement { 8 | [key: string]: any; 9 | 10 | constructor(parser: Parser) { 11 | switch (parser.lookahead?.type) { 12 | case Keyword.DO: 13 | case Keyword.WHILE: 14 | case Keyword.FOR: { 15 | Object.assign(this, new IterationStatement(parser)); 16 | break; 17 | } 18 | 19 | case Keyword.SWITCH: { 20 | Object.assign(this, new SwitchStatement(parser)); 21 | break; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/SwitchStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Expression } from "@parser/nodes/expressions/Expression"; 3 | import { Statement } from "@parser/nodes/statements/Statement"; 4 | import { StatementList } from "@parser/nodes/statements/StatementList"; 5 | import { Keyword } from "@vietscript/shared"; 6 | 7 | export class SwitchStatement { 8 | type = "SwitchStatement"; 9 | 10 | discriminant: Expression; 11 | 12 | cases: Array<{ 13 | type: "SwitchCase"; 14 | test: Expression | null; 15 | consequent: Array; 16 | }> = []; 17 | 18 | constructor(parser: Parser) { 19 | parser.eat(Keyword.SWITCH); 20 | parser.eat("("); 21 | 22 | this.discriminant = new Expression(parser); 23 | 24 | parser.eat(")"); 25 | parser.eat("{"); 26 | 27 | while (![Keyword.DEFAULT, "}"].includes(parser.lookahead?.type as string)) { 28 | let hasConsequent = false; 29 | 30 | parser.eat(Keyword.CASE); 31 | 32 | const test = new Expression(parser); 33 | 34 | parser.eat(":"); 35 | 36 | while (![Keyword.CASE, Keyword.DEFAULT, "}"].includes(parser.lookahead?.type as string)) { 37 | this.cases.push({ 38 | type: "SwitchCase", 39 | test, 40 | consequent: new StatementList(parser, [Keyword.CASE, Keyword.DEFAULT, "}"]).body, 41 | }); 42 | hasConsequent = true; 43 | } 44 | 45 | if (!hasConsequent) { 46 | this.cases.push({ 47 | type: "SwitchCase", 48 | test, 49 | consequent: [], 50 | }); 51 | } 52 | } 53 | 54 | if (parser.lookahead?.type === Keyword.DEFAULT) { 55 | parser.eat(Keyword.DEFAULT); 56 | parser.eat(":"); 57 | this.cases.push({ 58 | type: "SwitchCase", 59 | test: null, 60 | consequent: new StatementList(parser).body, 61 | }); 62 | } 63 | 64 | parser.eat("}"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/__test__/statement-breakable-switch.test.ts: -------------------------------------------------------------------------------- 1 | import { SwitchStatement } from "@parser/nodes/statements/breakable/SwitchStatement"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import toPlainObject from "../../../../toPlainObject"; 5 | 6 | describe("statement-breakable-switch.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse( 9 | ` 10 | duyệt (tuổi tác) { 11 | trường hợp 1: 12 | var xyz = 1; 13 | trường hợp 18: 14 | trường hợp 60: 15 | } 16 | `, 17 | SwitchStatement, 18 | ); 19 | 20 | expect(toPlainObject(result)).toStrictEqual({ 21 | type: "SwitchStatement", 22 | discriminant: { 23 | type: "Identifier", 24 | name: "_tu7893i_t225c", 25 | }, 26 | cases: [ 27 | { 28 | type: "SwitchCase", 29 | test: { 30 | type: "NumericLiteral", 31 | value: 1, 32 | extra: { 33 | rawValue: 1, 34 | raw: "1", 35 | }, 36 | start: 38, 37 | end: 39, 38 | }, 39 | consequent: [ 40 | { 41 | type: "VariableDeclaration", 42 | declarations: [ 43 | { 44 | type: "VariableDeclarator", 45 | id: { 46 | type: "Identifier", 47 | name: "_xyz", 48 | }, 49 | init: { 50 | type: "NumericLiteral", 51 | value: 1, 52 | extra: { 53 | rawValue: 1, 54 | raw: "1", 55 | }, 56 | start: 56, 57 | end: 57, 58 | }, 59 | }, 60 | ], 61 | kind: "var", 62 | }, 63 | ], 64 | }, 65 | { 66 | type: "SwitchCase", 67 | test: { 68 | type: "NumericLiteral", 69 | value: 18, 70 | extra: { 71 | rawValue: 18, 72 | raw: "18", 73 | }, 74 | start: 74, 75 | end: 76, 76 | }, 77 | consequent: [], 78 | }, 79 | { 80 | type: "SwitchCase", 81 | test: { 82 | type: "NumericLiteral", 83 | value: 60, 84 | extra: { 85 | rawValue: 60, 86 | raw: "60", 87 | }, 88 | start: 93, 89 | end: 95, 90 | }, 91 | consequent: [], 92 | }, 93 | ], 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/iteration/DoWhileStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 3 | import { Expression } from "@parser/nodes/expressions/Expression"; 4 | import { Keyword } from "@vietscript/shared"; 5 | 6 | export class DoWhileStatement { 7 | type = "DoWhileStatement"; 8 | 9 | body: BlockStatement; 10 | 11 | test: Expression; 12 | 13 | constructor(parser: Parser) { 14 | parser.eat(Keyword.DO); 15 | 16 | this.body = new BlockStatement(parser); 17 | 18 | parser.eat(Keyword.WHILE); 19 | 20 | parser.eat("("); 21 | 22 | this.test = new Expression(parser); 23 | 24 | parser.eat(")"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/iteration/ForInOfStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration"; 3 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 4 | 5 | export class ForInOfStatement { 6 | type: "ForInStatement" | "ForOfStatement"; 7 | 8 | await: boolean; 9 | 10 | left: VariableDeclaration | Identifier; 11 | 12 | right: Identifier; 13 | 14 | constructor(parser: Parser, isAsync: boolean) { 15 | this.await = isAsync; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration"; 3 | import { Statement } from "@parser/nodes/statements/Statement"; 4 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 5 | import { Expression } from "@parser/nodes/expressions/Expression"; 6 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 7 | import { Keyword } from "@vietscript/shared"; 8 | 9 | // import { ForInOfStatement } from "./ForInOfStatement"; 10 | 11 | export class ForStatement { 12 | type = "ForStatement"; 13 | 14 | init: VariableDeclaration | Identifier; 15 | 16 | test: Expression; 17 | 18 | update: Expression; 19 | 20 | body: Statement | BlockStatement; 21 | 22 | constructor(parser: Parser) { 23 | parser.eat(Keyword.FOR); 24 | 25 | let isAsync = false; 26 | 27 | if (parser.lookahead?.type === Keyword.AWAIT) { 28 | parser.eat(Keyword.AWAIT); 29 | isAsync = true; 30 | } 31 | 32 | parser.eat("("); 33 | 34 | if (!isAsync && parser.lookahead?.type !== Keyword.IN && parser.lookahead?.type !== Keyword.OF) { 35 | switch (parser.lookahead?.type) { 36 | case Keyword.VAR: 37 | case Keyword.LET: { 38 | this.init = new VariableDeclaration(parser); 39 | break; 40 | } 41 | case Keyword.CONST: { 42 | throw new Error("Const declarations are not allowed in for loops"); 43 | } 44 | default: { 45 | // TODO: LexicalDeclaration instead of Identifier 46 | this.init = new Identifier(parser); 47 | break; 48 | } 49 | } 50 | 51 | parser.eat(";"); 52 | 53 | this.test = new Expression(parser); 54 | 55 | if (parser.lookahead?.type === ";") { 56 | parser.eat(";"); 57 | } 58 | 59 | if (parser.lookahead?.type !== ")") { 60 | this.update = new Expression(parser); 61 | } 62 | } else { 63 | // Contains only type, left and right as LeftHandSideExpression 64 | // Object.assign(this, new ForInOfStatement(parser, isAsync)); 65 | throw new Error("For loop with in or of is not implemented"); 66 | } 67 | 68 | parser.eat(")"); 69 | 70 | this.body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/iteration/IterationStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { DoWhileStatement } from "./DoWhileStatement"; 5 | import { WhileStatement } from "./WhileStatement"; 6 | import { ForStatement } from "./ForStatement"; 7 | 8 | export class IterationStatement { 9 | [key: string]: any; 10 | 11 | constructor(parser: Parser) { 12 | switch (parser.lookahead?.type) { 13 | case Keyword.DO: { 14 | Object.assign(this, new DoWhileStatement(parser)); 15 | break; 16 | } 17 | case Keyword.WHILE: { 18 | Object.assign(this, new WhileStatement(parser)); 19 | break; 20 | } 21 | case Keyword.FOR: { 22 | Object.assign(this, new ForStatement(parser)); 23 | break; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/breakable/iteration/WhileStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Statement } from "@parser/nodes/statements/Statement"; 3 | import { BlockStatement } from "@parser/nodes/statements/BlockStatement"; 4 | import { Expression } from "@parser/nodes/expressions/Expression"; 5 | import { Keyword } from "@vietscript/shared"; 6 | 7 | export class WhileStatement { 8 | type = "WhileStatement"; 9 | 10 | body: Statement | BlockStatement; 11 | 12 | test: Expression; 13 | 14 | constructor(parser: Parser) { 15 | parser.eat(Keyword.WHILE); 16 | 17 | parser.eat("("); 18 | 19 | this.test = new Expression(parser); 20 | 21 | parser.eat(")"); 22 | 23 | this.body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/try/CatchClause.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Identifier } from "@parser/nodes/identifier/Identifier"; 3 | import { Keyword } from "@vietscript/shared"; 4 | 5 | import { BlockStatement } from "../BlockStatement"; 6 | 7 | export class CatchClause { 8 | type = "CatchClause"; 9 | 10 | body: BlockStatement; 11 | 12 | param: Identifier | null = null; 13 | 14 | constructor(parser: Parser) { 15 | parser.eat(Keyword.CATCH); 16 | 17 | if (parser.lookahead?.type === "(") { 18 | parser.eat("("); 19 | this.param = new Identifier(parser); 20 | parser.eat(")"); 21 | } 22 | 23 | this.body = new BlockStatement(parser); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/try/TryStatement.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | import { BlockStatement } from "../BlockStatement"; 5 | 6 | import { CatchClause } from "./CatchClause"; 7 | 8 | export class TryStatement { 9 | type = "TryStatement"; 10 | 11 | block: BlockStatement; 12 | 13 | handler: CatchClause | null = null; 14 | 15 | finalizer: BlockStatement | null = null; 16 | 17 | constructor(parser: Parser) { 18 | parser.eat(Keyword.TRY); 19 | 20 | this.block = new BlockStatement(parser); 21 | 22 | if (parser.lookahead?.type !== Keyword.CATCH && parser.lookahead?.type !== Keyword.FINALLY) { 23 | throw new Error("Expected Catch or Finally"); 24 | } 25 | 26 | if (parser.lookahead?.type === Keyword.CATCH) { 27 | this.handler = new CatchClause(parser); 28 | } 29 | 30 | if (parser.lookahead?.type === Keyword.FINALLY) { 31 | parser.eat(Keyword.FINALLY); 32 | this.finalizer = new BlockStatement(parser); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/parser/src/nodes/statements/try/__test__/statement-try.test.ts: -------------------------------------------------------------------------------- 1 | import { TryStatement } from "@parser/nodes/statements/try/TryStatement"; 2 | 3 | import parser from "../../../../setup-test"; 4 | import toPlainObject from "../../../../toPlainObject"; 5 | 6 | describe("statement-try.test", () => { 7 | it("should parse the syntax normally", () => { 8 | const result = parser.parse( 9 | ` 10 | thử { 11 | khai báo gì đó; 12 | } bắt lỗi (lỗi) { 13 | khai báo gì đó khác; 14 | } cuối cùng { 15 | khai báo gì đó khác nữa; 16 | }; 17 | `, 18 | TryStatement, 19 | ); 20 | 21 | expect(toPlainObject(result)).toStrictEqual({ 22 | type: "TryStatement", 23 | block: { 24 | type: "BlockStatement", 25 | directives: [], 26 | body: [ 27 | { 28 | type: "VariableDeclaration", 29 | declarations: [ 30 | { 31 | type: "VariableDeclarator", 32 | id: { 33 | type: "Identifier", 34 | name: "_g236_273243", 35 | }, 36 | init: { 37 | type: "Identifier", 38 | name: "undefined", 39 | }, 40 | }, 41 | ], 42 | kind: "var", 43 | }, 44 | ], 45 | }, 46 | handler: { 47 | type: "CatchClause", 48 | body: { 49 | type: "BlockStatement", 50 | directives: [], 51 | body: [ 52 | { 53 | type: "VariableDeclaration", 54 | declarations: [ 55 | { 56 | type: "VariableDeclarator", 57 | id: { 58 | type: "Identifier", 59 | name: "_g236_273243_kh225c", 60 | }, 61 | init: { 62 | type: "Identifier", 63 | name: "undefined", 64 | }, 65 | }, 66 | ], 67 | kind: "var", 68 | }, 69 | ], 70 | }, 71 | param: { 72 | type: "Identifier", 73 | name: "_l7895i", 74 | }, 75 | }, 76 | finalizer: { 77 | type: "BlockStatement", 78 | directives: [], 79 | body: [ 80 | { 81 | type: "VariableDeclaration", 82 | declarations: [ 83 | { 84 | type: "VariableDeclarator", 85 | id: { 86 | type: "Identifier", 87 | name: "_g236_273243_kh225c_n7919a", 88 | }, 89 | init: { 90 | type: "Identifier", 91 | name: "undefined", 92 | }, 93 | }, 94 | ], 95 | kind: "var", 96 | }, 97 | ], 98 | }, 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /packages/parser/src/parser.ts: -------------------------------------------------------------------------------- 1 | import { Keyword, Token } from "@vietscript/shared"; 2 | 3 | import { Tokenizer } from "./tokenizer"; 4 | import { Program } from "./nodes/Program"; 5 | 6 | export class Parser { 7 | public syntax: string; 8 | 9 | public tokenizer: Tokenizer; 10 | 11 | public lookahead: Token | null; 12 | 13 | /** 14 | * Initializes the parser. 15 | */ 16 | constructor() { 17 | this.syntax = ""; 18 | this.tokenizer = new Tokenizer(this); 19 | this.lookahead = null; 20 | } 21 | 22 | public parse(syntax: string, InitAtsNodeClass?: any): any; 23 | 24 | /** 25 | * Parse a Formkl syntax string into Formkl object 26 | */ 27 | public parse(syntax: string) { 28 | this.lookahead = null; 29 | this.syntax = ""; 30 | 31 | this.syntax = syntax; 32 | this.tokenizer = new Tokenizer(this); 33 | 34 | this.lookahead = this.tokenizer.getNextToken(); 35 | 36 | return new Program(this); 37 | } 38 | 39 | /** 40 | * Expects a token of a given type. 41 | */ 42 | eat(tokenType: Token["type"]) { 43 | const token = this.lookahead; 44 | 45 | if (token === null) { 46 | throw new SyntaxError(`Unexpected end of input, expected: "${tokenType}"`); 47 | } 48 | 49 | if (token.type !== tokenType) { 50 | switch (tokenType) { 51 | case Keyword.IDENTIFIER: { 52 | throw new SyntaxError( 53 | `Unexpected token: "${token.value}", cannot use keyword "${token.value}" for the beginning of the identifer`, 54 | ); 55 | } 56 | default: { 57 | throw new SyntaxError(`Unexpected token: "${token.value}", expected: "${tokenType}"`); 58 | } 59 | } 60 | } 61 | 62 | this.lookahead = this.tokenizer.getNextToken(); 63 | 64 | return token; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/parser/src/setup-test.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Tokenizer } from "@parser/tokenizer"; 3 | 4 | class SingleNodeParser extends Parser { 5 | parse(syntax: string, InitAtsNodeClass: any) { 6 | this.lookahead = null; 7 | this.syntax = ""; 8 | 9 | this.syntax = syntax; 10 | this.tokenizer = new Tokenizer(this); 11 | 12 | // Prime the tokenizer to obtain the first 13 | // token which is our lookahead. The lookahead is 14 | // used for predective parsing. 15 | 16 | this.lookahead = this.tokenizer.getNextToken(); 17 | 18 | return new InitAtsNodeClass(this); 19 | } 20 | } 21 | 22 | export default new SingleNodeParser(); 23 | -------------------------------------------------------------------------------- /packages/parser/src/toPlainObject.ts: -------------------------------------------------------------------------------- 1 | export default (object: any) => JSON.parse(JSON.stringify(object)); 2 | -------------------------------------------------------------------------------- /packages/parser/src/tokenizer.ts: -------------------------------------------------------------------------------- 1 | import { Token } from "@vietscript/shared"; 2 | 3 | import { Specs } from "./constants/specs"; 4 | import { Parser } from "./parser"; 5 | 6 | /** 7 | * Tokenizer spec. 8 | */ 9 | 10 | /** 11 | * Tokenizer class 12 | * Lazily pulls a token from a stream. 13 | */ 14 | export class Tokenizer { 15 | private parser: Parser; 16 | 17 | private cursor: number; 18 | 19 | /** 20 | * Initializes the string. 21 | */ 22 | constructor(parser: Parser) { 23 | this.parser = parser; 24 | this.cursor = 0; // track the position of each character 25 | } 26 | 27 | /** 28 | * Whether the tokenizer reached EOF. 29 | */ 30 | public isEOF() { 31 | return this.cursor === this.parser.syntax.length; 32 | } 33 | 34 | /** 35 | * Whether we still have more tokens. 36 | */ 37 | protected hasMoreTokens() { 38 | return this.cursor < this.parser.syntax.length; 39 | } 40 | 41 | /** 42 | * Obtains next token. 43 | */ 44 | public getNextToken(): Token | null { 45 | if (!this.hasMoreTokens()) { 46 | return null; 47 | } 48 | const string = this.parser.syntax.slice(this.cursor); 49 | 50 | for (const [regexp, tokenType] of Specs) { 51 | const tokenValue = this.match(regexp, string); 52 | 53 | // Couldn't match this rule, continue. 54 | if (tokenValue === null) { 55 | continue; 56 | } 57 | 58 | // Should skip this null token because could be a whitespace or something else 59 | if (tokenType === null) { 60 | return this.getNextToken(); 61 | } 62 | 63 | // We return the token 64 | return { 65 | type: tokenType, 66 | value: tokenValue, 67 | start: this.cursor - String(tokenValue).length, 68 | end: this.cursor, 69 | }; 70 | } 71 | 72 | throw new SyntaxError(`Unexpected token: "${string[0]}"`); 73 | } 74 | 75 | /** 76 | * Matches a token for a regular expression. 77 | */ 78 | private match(regexp: RegExp, syntax: string) { 79 | const formattedSyntax = syntax.split(";"); 80 | const matched = regexp.exec(formattedSyntax[0].concat(";")); 81 | 82 | if (matched && matched.index === 0) { 83 | this.cursor += matched[0].length; 84 | return matched[0]; 85 | } 86 | 87 | return null; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/parser/src/utils/is-expression.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "@parser/parser"; 2 | import { Keyword } from "@vietscript/shared"; 3 | 4 | export const isExpression = (parser: Parser): boolean => { 5 | switch (parser.lookahead?.type) { 6 | case Keyword.ASYNC: 7 | case Keyword.FUNCTION: 8 | case "[": 9 | case "{": 10 | case Keyword.NUMBER: 11 | case Keyword.STRING: 12 | case Keyword.BOOLEAN: 13 | case Keyword.NAN: 14 | case Keyword.NULL: 15 | case Keyword.UNDEFINED: 16 | case "++": 17 | case "--": 18 | case "delete": 19 | case "void": 20 | case "typeof": 21 | case "+": 22 | case "-": 23 | case "~": 24 | case "!": 25 | case Keyword.AWAIT: 26 | case Keyword.THIS: 27 | case Keyword.IDENTIFIER: { 28 | return true; 29 | } 30 | default: { 31 | return false; 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /packages/parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist", 6 | "types": [ 7 | "node", 8 | "vite/client", 9 | "vitest/globals" 10 | ], 11 | "paths": { 12 | "@parser": [ 13 | "./src/index.ts" 14 | ], 15 | "@parser/*": [ 16 | "./src/*" 17 | ] 18 | } 19 | }, 20 | "include": [ 21 | "./**/*.ts" 22 | ], 23 | "exclude": [ 24 | "./node_modules", 25 | "./dist" 26 | ] 27 | } -------------------------------------------------------------------------------- /packages/parser/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import dts from "vite-plugin-dts"; 4 | import { defineConfig } from "vite"; 5 | 6 | export default defineConfig({ 7 | resolve: { 8 | alias: { 9 | "@parser": path.resolve("./src"), 10 | }, 11 | }, 12 | build: { 13 | emptyOutDir: false, 14 | lib: { 15 | name: "@vietscript/parser", 16 | entry: path.resolve("./src/index.ts"), 17 | formats: ["es", "cjs", "umd"], 18 | fileName: "index", 19 | }, 20 | rollupOptions: { 21 | external: [], 22 | 23 | output: { 24 | exports: "named", 25 | globals: {}, 26 | }, 27 | }, 28 | }, 29 | plugins: [ 30 | dts({ 31 | root: ".", 32 | entryRoot: "./src", 33 | outputDir: "./dist/types", 34 | }), 35 | ], 36 | }); 37 | -------------------------------------------------------------------------------- /packages/plugins/vite/.gitignore: -------------------------------------------------------------------------------- 1 | ./dist 2 | ./node_modules -------------------------------------------------------------------------------- /packages/plugins/vite/.npmignore: -------------------------------------------------------------------------------- 1 | ./node_modules 2 | -------------------------------------------------------------------------------- /packages/plugins/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vietscript/plugin-vite", 3 | "description": "A VietScript Vite plugin to load .vjs files.", 4 | "version": "0.0.1", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/index.js", 9 | "require": "./dist/index.cjs" 10 | } 11 | }, 12 | "main": "./dist/index.js", 13 | "types": "./dist/types/index.d.ts", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/imrim12/vietscript.git", 17 | "directory": "packages/plugins/vite" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "typecheck": "tsc --noEmit", 28 | "build": "rimraf ./dist && vite build" 29 | }, 30 | "devDependencies": { 31 | "@vitest/ui": "^0.24.5", 32 | "rimraf": "^3.0.2", 33 | "typescript": "^4.9.4", 34 | "vite": "^4.0.4", 35 | "vite-plugin-dts": "^1.6.6", 36 | "vitest": "^0.28.3" 37 | }, 38 | "dependencies": { 39 | "@vietscript/parser": "workspace:*" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/plugins/vite/src/index.ts: -------------------------------------------------------------------------------- 1 | import parser from "@vietscript/parser"; 2 | 3 | export default () => ({ 4 | name: "vietscript-loader", 5 | transform(code: string, id: string) { 6 | if (id.endsWith(".vjs")) { 7 | let loadedFormModule = {}; 8 | 9 | try { 10 | loadedFormModule = parser.parse(code); 11 | } catch (error) { 12 | console.error(error); 13 | } 14 | 15 | return `export default ${JSON.stringify(loadedFormModule)}`; 16 | } 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /packages/plugins/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist", 6 | "types": ["vite/client", "vitest/globals"] 7 | }, 8 | "include": ["./**/*.ts", "./**/*.tsx", "./**/*.d.ts"], 9 | "exclude": ["./node_modules", "./dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugins/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import dts from "vite-plugin-dts"; 4 | import { defineConfig } from "vite"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | emptyOutDir: false, 9 | lib: { 10 | name: "@vietscript/plugin-vite", 11 | entry: path.resolve("./src/index.ts"), 12 | formats: ["es", "cjs"], 13 | fileName: "index", 14 | }, 15 | rollupOptions: { 16 | external: ["@vietscript/parser", "fs", "path"], 17 | output: { 18 | exports: "named", 19 | globals: {}, 20 | }, 21 | }, 22 | }, 23 | plugins: [ 24 | dts({ 25 | root: ".", 26 | entryRoot: "./src", 27 | outputDir: "./dist/types", 28 | }), 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /packages/plugins/webpack/.gitignore: -------------------------------------------------------------------------------- 1 | ./dist 2 | ./node_modules -------------------------------------------------------------------------------- /packages/plugins/webpack/.npmignore: -------------------------------------------------------------------------------- 1 | ./node_modules 2 | -------------------------------------------------------------------------------- /packages/plugins/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vietscript/plugin-webpack", 3 | "version": "0.0.1", 4 | "description": "A VietScript plugin to include .vjs file into Webpack build", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/index.js", 9 | "require": "./dist/index.cjs" 10 | } 11 | }, 12 | "main": "./dist/index.js", 13 | "types": "./dist/types/index.d.ts", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/imrim12/vietscript.git", 17 | "directory": "packages/plugins/webpack" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "typecheck": "tsc --noEmit", 28 | "build": "rimraf ./dist && vite build" 29 | }, 30 | "devDependencies": { 31 | "@vitest/ui": "^0.24.5", 32 | "rimraf": "^3.0.2", 33 | "typescript": "^4.9.4", 34 | "vite": "^4.0.4", 35 | "vite-plugin-dts": "^1.6.6", 36 | "vitest": "^0.28.3", 37 | "webpack": "^5.75.0", 38 | "webpack-cli": "^5.0.1" 39 | }, 40 | "dependencies": { 41 | "@vietscript/parser": "workspace:*" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/plugins/webpack/src/index.ts: -------------------------------------------------------------------------------- 1 | import parser from "@vietscript/parser"; 2 | 3 | export default function (source: string) { 4 | const form = parser.parse(source); 5 | 6 | return `export default ${JSON.stringify(form)}`; 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugins/webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist", 6 | "types": ["vite/client", "vitest/globals"] 7 | }, 8 | "include": ["./**/*.ts", "./**/*.tsx", "./**/*.d.ts"], 9 | "exclude": ["./node_modules", "./dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugins/webpack/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import dts from "vite-plugin-dts"; 4 | import { defineConfig } from "vite"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | emptyOutDir: false, 9 | lib: { 10 | name: "@vietscript/plugin-webpack", 11 | entry: path.resolve("./src/index.ts"), 12 | formats: ["es", "cjs"], 13 | fileName: "index", 14 | }, 15 | rollupOptions: { 16 | external: ["@vietscript/parser", "fs", "path"], 17 | output: { 18 | exports: "named", 19 | globals: {}, 20 | }, 21 | }, 22 | }, 23 | plugins: [ 24 | dts({ 25 | root: ".", 26 | entryRoot: "./src", 27 | outputDir: "./dist/types", 28 | }), 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /packages/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./parser/keyword.enum"; 2 | export * from "./parser/node.interface"; 3 | export * from "./parser/operator.type"; 4 | export * from "./parser/token.type"; 5 | export * from "./parser/spec.type"; 6 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vietscript/shared", 3 | "version": "0.0.1", 4 | "main": "./index.ts", 5 | "files": [ 6 | "./" 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/imrim12/vietscript.git", 11 | "directory": "packages/shared" 12 | }, 13 | "publishConfig": { 14 | "access": "public", 15 | "registry": "https://registry.npmjs.org" 16 | }, 17 | "devDependencies": { 18 | "@vitest/ui": "^0.24.5", 19 | "rimraf": "^3.0.2", 20 | "typescript": "^4.9.4", 21 | "vite": "^4.0.4", 22 | "vite-plugin-dts": "^1.6.6", 23 | "vitest": "^0.28.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/shared/parser/keyword.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Keyword { 2 | VAR = "Var", 3 | CONST = "Const", 4 | LET = "Let", 5 | IF = "If", 6 | ELSE = "Else", 7 | FOR = "For", 8 | WHILE = "While", 9 | DO = "Do", 10 | BREAK = "Break", 11 | CONTINUE = "Continue", 12 | RETURN = "Return", 13 | SWITCH = "Switch", 14 | CASE = "Case", 15 | DEFAULT = "Default", 16 | TRY = "Try", 17 | CATCH = "Catch", 18 | FINALLY = "Finally", 19 | THROW = "Throw", 20 | FUNCTION = "Function", 21 | CLASS = "Class", 22 | INTERFACE = "Interface", 23 | ENUM = "Enum", 24 | TYPE = "Type", 25 | IMPORT = "Import", 26 | EXPORT = "Export", 27 | FROM = "From", 28 | AS = "As", 29 | IN = "In", 30 | OF = "Of", 31 | IS = "Is", 32 | NEW = "New", 33 | THIS = "This", 34 | SUPER = "Super", 35 | TRUE = "True", 36 | FALSE = "False", 37 | NULL = "Null", 38 | VOID = "Void", 39 | ANY = "Any", 40 | NUMBER = "Number", 41 | STRING = "String", 42 | BOOLEAN = "Boolean", 43 | OBJECT = "Object", 44 | ARRAY = "Array", 45 | NAN = "NaN", 46 | INFINITY = "Infinity", 47 | UNDEFINED = "Undefined", 48 | STATIC = "Static", 49 | PUBLIC = "Public", 50 | PRIVATE = "Private", 51 | PROTECTED = "Protected", 52 | ABSTRACT = "Abstract", 53 | EXTENDS = "Extends", 54 | IMPLEMENTS = "Implements", 55 | GET = "Get", 56 | SET = "Set", 57 | CONSTRUCTOR = "Constructor", 58 | YIELD = "Yield", 59 | AWAIT = "Await", 60 | ASYNC = "Async", 61 | DELETE = "Delete", 62 | TYPEOF = "Typeof", 63 | INSTANCEOF = "Instanceof", 64 | WITH = "With", 65 | DEBUGGER = "Debugger", 66 | IDENTIFIER = "Identifier", 67 | } 68 | -------------------------------------------------------------------------------- /packages/shared/parser/node.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Node { 2 | type: string; 3 | start: number; 4 | end: number; 5 | } 6 | -------------------------------------------------------------------------------- /packages/shared/parser/operator.type.ts: -------------------------------------------------------------------------------- 1 | export type Operator = 2 | | "[" 3 | | "]" 4 | | "(" 5 | | ")" 6 | | "{" 7 | | "}" 8 | | ";" 9 | | "," 10 | | "===" 11 | | "==" 12 | | "!==" 13 | | "!=" 14 | | "=" 15 | | "?" 16 | | "?." 17 | | ":" 18 | | "..." 19 | | "." 20 | | "++" 21 | | "+" 22 | | "--" 23 | | "-" 24 | | "~" 25 | | "!" 26 | | "*" 27 | | "/" 28 | | "%" 29 | | "**" 30 | | "??" 31 | | "#" 32 | | ">>>=" 33 | | ">>>" 34 | | ">>" 35 | | ">>=" 36 | | "<<<" 37 | | "<<=" 38 | | "<<" 39 | | "<=" 40 | | ">=" 41 | | "<" 42 | | ">" 43 | | "&&" 44 | | "||" 45 | | "&" 46 | | "^" 47 | | "|" 48 | | "*=" 49 | | "/=" 50 | | "%=" 51 | | "+=" 52 | | "-=" 53 | | "&=" 54 | | "^=" 55 | | "|=" 56 | | "**=" 57 | | "=>" 58 | | "in" 59 | | "instanceof" 60 | | "typeof" 61 | | "void" 62 | | "delete"; 63 | -------------------------------------------------------------------------------- /packages/shared/parser/spec.type.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "./keyword.enum"; 2 | import { Operator } from "./operator.type"; 3 | 4 | export type Spec = [RegExp, Keyword | Operator | null]; 5 | -------------------------------------------------------------------------------- /packages/shared/parser/token.type.ts: -------------------------------------------------------------------------------- 1 | import { Keyword } from "./keyword.enum"; 2 | import { Operator } from "./operator.type"; 3 | 4 | export type Token = { 5 | type: Keyword | Operator | null; 6 | value: string | number; 7 | start: number; 8 | end: number; 9 | }; 10 | -------------------------------------------------------------------------------- /playground/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 -------------------------------------------------------------------------------- /playground/.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_URL -------------------------------------------------------------------------------- /playground/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /playground/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path') 3 | 4 | module.exports = { 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:react/recommended', 8 | 'plugin:react-hooks/recommended', 9 | 'plugin:import/recommended', 10 | 'plugin:import/typescript', 11 | 'plugin:jsx-a11y/recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | 'eslint-config-prettier', 14 | 'prettier' 15 | ], 16 | plugins: ['prettier'], 17 | settings: { 18 | react: { 19 | version: 'detect' 20 | }, 21 | 'import/resolver': { 22 | node: { 23 | paths: [path.resolve(__dirname, '')], 24 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 25 | } 26 | } 27 | }, 28 | env: { 29 | node: true 30 | }, 31 | rules: { 32 | 'react/react-in-jsx-scope': 'off', 33 | 'react/jsx-no-target-blank': 'warn', 34 | 'prettier/prettier': [ 35 | 'warn', 36 | { 37 | arrowParens: 'always', 38 | semi: false, 39 | trailingComma: 'none', 40 | tabWidth: 2, 41 | endOfLine: 'auto', 42 | useTabs: false, 43 | singleQuote: true, 44 | printWidth: 120, 45 | jsxSingleQuote: true 46 | } 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env 27 | 28 | yarn.lock -------------------------------------------------------------------------------- /playground/.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{ts, tsx, js, jsx}": ["prettier --write", "eslint --fix", "eslint"], 3 | "*.json": ["prettier --write"] 4 | } 5 | -------------------------------------------------------------------------------- /playground/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /playground/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "semi": false, 4 | "trailingComma": "none", 5 | "tabWidth": 2, 6 | "endOfLine": "auto", 7 | "useTabs": false, 8 | "singleQuote": true, 9 | "printWidth": 120, 10 | "jsxSingleQuote": true 11 | } 12 | -------------------------------------------------------------------------------- /playground/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imrim12/vietscript/10fb9f777f393db35e66b9c7854287d89017ff84/playground/favicon.ico -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vietscript Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "homepage": "https://h-vien.github.io/react-ts-poilerplate", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "predeploy": "npm run build", 9 | "deploy": "gh-pages -d dist", 10 | "dev": "vite", 11 | "build": "tsc && vite build", 12 | "test": "vite test", 13 | "preview": "vite preview", 14 | "lint": "eslint --ext ts,tsx src/", 15 | "lint:fix": "eslint --fix --ext ts,tsx src/", 16 | "prettier": "prettier --check \"src/**/(*.tsx|*.ts|*.css|*.scss)\"", 17 | "prettier:fix": "prettier --write \"src/**/(*.tsx|*.ts|*.css|*.scss)\"", 18 | "lint-staged": "lint-staged", 19 | "lint-pass": "echo 'Format code complete'" 20 | }, 21 | "dependencies": { 22 | "@ant-design/icons": "^5.0.1", 23 | "@monaco-editor/react": "^4.6.0", 24 | "@supabase/supabase-js": "^2.43.1", 25 | "@uiw/react-textarea-code-editor": "^3.0.2", 26 | "antd": "^5.5.0", 27 | "axios": "^1.3.4", 28 | "dayjs": "^1.11.8", 29 | "lodash": "^4.17.21", 30 | "lodash-es": "^4.17.21", 31 | "monaco-editor": "^0.48.0", 32 | "monaco-editor-core": "^0.48.0", 33 | "react": "^18.2.0", 34 | "react-dom": "^18.2.0", 35 | "react-query": "^3.39.3", 36 | "react-router-dom": "^6.9.0", 37 | "react-toastify": "^9.1.2", 38 | "rollup-plugin-visualizer": "^5.9.0", 39 | "typescript": "^5.4.3", 40 | "vitest": "^0.29.8" 41 | }, 42 | "pre-commit": [ 43 | "check-types", 44 | "lint-staged", 45 | "lint-pass", 46 | "test" 47 | ], 48 | "devDependencies": { 49 | "@babel/generator": "^7.24.4", 50 | "@types/lodash": "^4.14.192", 51 | "@types/node": "^18.15.11", 52 | "@types/react": "^18.0.28", 53 | "@types/react-dom": "^18.0.11", 54 | "@typescript-eslint/eslint-plugin": "^5.57.0", 55 | "@typescript-eslint/parser": "^5.57.0", 56 | "@vietscript/plugin-vite": "workspace:*", 57 | "@vietscript/parser": "workspace:*", 58 | "@vitejs/plugin-react": "^3.1.0", 59 | "autoprefixer": "^10.4.14", 60 | "eslint": "^8.37.0", 61 | "eslint-config-prettier": "^8.8.0", 62 | "eslint-plugin-import": "^2.27.5", 63 | "eslint-plugin-jsx-a11y": "^6.7.1", 64 | "eslint-plugin-prettier": "^4.2.1", 65 | "eslint-plugin-react": "^7.32.2", 66 | "eslint-plugin-react-hooks": "^4.6.0", 67 | "gh-pages": "^6.0.0", 68 | "husky": "^8.0.0", 69 | "lint-staged": "^14.0.1", 70 | "postcss": "^8.4.21", 71 | "prettier": "^2.8.7", 72 | "prettier-plugin-tailwindcss": "^0.2.6", 73 | "tailwindcss": "^3.3.0", 74 | "vite": "^4.2.0", 75 | "vite-plugin-node-polyfills": "^0.21.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /playground/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ToastContainer } from "react-toastify"; 2 | import "react-toastify/dist/ReactToastify.css"; 3 | import useRouteElements from "./useRoutesElement"; 4 | import { ConfigProvider } from "antd"; 5 | import { theme } from "./configs/antd.config"; 6 | 7 | function App() { 8 | const routeElements = useRouteElements(); 9 | return ( 10 | <> 11 | 12 | {routeElements} 13 | 23 | 24 | 25 | ); 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /playground/src/components/Results.tsx: -------------------------------------------------------------------------------- 1 | import { CaretRightOutlined } from "@ant-design/icons"; 2 | import { Button, Tabs, TabsProps } from "antd"; 3 | import { useEffect, useState } from "react"; 4 | import { supabase } from "src/utils/supabase"; 5 | import TestCases from "./TestCases"; 6 | import TestResults from "./TestResults"; 7 | import { formatFunctionName } from "src/utils"; 8 | import { RESULTS_TAB_KEY } from "src/constants"; 9 | import { Result, TestCase } from "src/types"; 10 | import { toast } from "react-toastify"; 11 | import parser from "@vietscript/parser"; 12 | import generate from "@babel/generator"; 13 | 14 | interface Props { 15 | id: string; 16 | program?: string; 17 | fnName: string; 18 | } 19 | 20 | export default function Results({ id, program, fnName }: Props) { 21 | const [testCases, setTestCases] = useState([{ input: "", expectedOutput: "" }]); 22 | const [time, setTime] = useState(0); 23 | const [activeTab, setActiveTab] = useState(RESULTS_TAB_KEY.TEST_CASES); 24 | const [results, setResults] = useState([]); 25 | 26 | const items: TabsProps["items"] = [ 27 | { 28 | key: RESULTS_TAB_KEY.TEST_CASES, 29 | label: "Các trường hợp kiểm thử", 30 | children: , 31 | }, 32 | { 33 | key: RESULTS_TAB_KEY.TEST_RESULTS, 34 | label: "Kết quả", 35 | disabled: activeTab !== RESULTS_TAB_KEY.TEST_RESULTS, 36 | children: , 37 | }, 38 | ]; 39 | 40 | const getTestCases = async () => { 41 | if (!id) return; 42 | const { data, error } = await supabase.from("test_cases").select("*").eq("problem_id", id); 43 | 44 | if (error) toast.error(error.message); 45 | setTestCases(data?.[0].test_cases); 46 | }; 47 | 48 | useEffect(() => { 49 | setActiveTab(RESULTS_TAB_KEY.TEST_CASES); 50 | getTestCases(); 51 | }, [id]); 52 | 53 | const runTests = (testCases: TestCase[]) => { 54 | if (!program) return []; 55 | const results = testCases.map((testCase) => { 56 | let result; 57 | try { 58 | const ast = parser.parse(program); 59 | const codeGenerated = generate(ast); 60 | const func = new Function("input", codeGenerated.code + `\nreturn ${formatFunctionName(fnName)}(input);`); 61 | const output = func(JSON.parse(testCase.input)); 62 | result = String(output); 63 | } catch (error) { 64 | result = `Error: ${error}`; 65 | } 66 | return { ...testCase, result }; 67 | }); 68 | setActiveTab(RESULTS_TAB_KEY.TEST_RESULTS); 69 | return results; 70 | }; 71 | 72 | const handleRunCode = () => { 73 | const timeStart = performance.now(); 74 | const results = runTests(testCases.slice(0, 4)); 75 | setResults(results); 76 | const timeEnd = performance.now(); 77 | setTime(timeEnd - timeStart); 78 | }; 79 | 80 | return ( 81 |
82 | 85 | 86 | setActiveTab(key)} activeKey={String(activeTab)} items={items} /> 87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /playground/src/components/TestCases.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "antd"; 2 | import { TestCase } from "src/types"; 3 | 4 | interface Props { 5 | testCases: TestCase[]; 6 | } 7 | const TestCases = ({ testCases }: Props) => { 8 | return ( 9 |
10 | {testCases && ( 11 | { 15 | const id = String(i + 1); 16 | return { 17 | label: `Kiểm thử ${id}`, 18 | key: id, 19 | children: ( 20 | <> 21 | Đầu vào 22 |
23 |
{testCase.input}
24 |
25 |
26 | Kết quả mong muốn 27 |
28 |
{testCase.expectedOutput}
29 |
30 | 31 | ), 32 | }; 33 | })} 34 | /> 35 | )} 36 |
37 | ); 38 | }; 39 | 40 | export default TestCases; 41 | -------------------------------------------------------------------------------- /playground/src/components/TestResults.tsx: -------------------------------------------------------------------------------- 1 | import { ClockCircleOutlined } from "@ant-design/icons"; 2 | import { Button, Segmented, Tabs } from "antd"; 3 | import { useState } from "react"; 4 | import { Result } from "src/types"; 5 | 6 | interface Props { 7 | results: Result[]; 8 | time?: number; 9 | } 10 | 11 | export default function TestResults({ results, time }: Props) { 12 | const [activeTab, setActiveTab] = useState("1"); 13 | return ( 14 |
15 |
16 | {results.every((result) => String(result.result) === result.expectedOutput) ? ( 17 |

Thành công

18 | ) : ( 19 |

Thất bại

20 | )} 21 | 24 |
25 | {results && ( 26 | 27 | options={results.map((result, i) => ({ 28 | label: ( 29 |
30 | 35 |

{`Kiểm thử ${i + 1}`}

36 |
37 | ), 38 | value: String(i + 1), 39 | }))} 40 | onChange={(value) => { 41 | setActiveTab(value); 42 | }} 43 | /> 44 | )} 45 | {activeTab && ( 46 |
47 | Đầu vào 48 |
49 |
{results[Number(activeTab) - 1]?.input}
50 |
51 |
52 | Kết quả 53 |
60 |
{results[Number(activeTab) - 1]?.result}
61 |
62 |
63 | Kết quả mong muốn 64 |
65 |
{results[Number(activeTab) - 1]?.expectedOutput}
66 |
67 |
68 | )} 69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /playground/src/components/layouts/DefaultLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactWithChild } from "src/interface/app"; 2 | 3 | export default function DefaultLayout({ children }: ReactWithChild) { 4 | return <>{children}; 5 | } 6 | -------------------------------------------------------------------------------- /playground/src/configs/antd.config.ts: -------------------------------------------------------------------------------- 1 | export const theme = { 2 | token: { 3 | // Seed Token 4 | colorPrimary: '#00BD9B', 5 | colorLinkHover: '#4F8E6E', 6 | fontFamily: 'Roboto' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playground/src/configs/index.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | baseUrl: import.meta.env.VITE_API_URL || '', 3 | supabaseKey: import.meta.env.VITE_SUPABASE_ANON || '', 4 | supabaseUrl: import.meta.env.VITE_SUPABASE_URL || '', 5 | maxSizeUploadAvatar: 1048576 // bytes 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /playground/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const RESULTS_TAB_KEY = { 2 | TEST_CASES: "TEST_CASES", 3 | TEST_RESULTS: "TEST_RESULTS", 4 | }; 5 | -------------------------------------------------------------------------------- /playground/src/editor/autocomplete.ts: -------------------------------------------------------------------------------- 1 | const genKeywords = (keywords: string[], kind: any, range: any) => { 2 | return keywords.map((keyword) => { 3 | return { 4 | label: keyword, 5 | kind, 6 | insertText: keyword, 7 | range: range 8 | } 9 | }) 10 | } 11 | 12 | export const KEYWORDS = [ 13 | 'khai báo', 14 | 'hằng số', 15 | 'hàm', 16 | 'trường hợp', 17 | 'trả về', 18 | 'duyệt', 19 | 'ngược lại', 20 | 'nếu', 21 | 'bắt lỗi', 22 | 'tạo mới', 23 | 'cuối cùng', 24 | 'trả về', 25 | 'tiếp tục', 26 | 'lặp', 27 | 'khi mà', 28 | 'hàm', 29 | 'mặc định', 30 | 'báo lỗi', 31 | 'xoá', 32 | 'trong', 33 | 'từ', 34 | 'in ra' 35 | ] 36 | 37 | export function createDependencyProposals(range: any, monaco: any) { 38 | return [ 39 | ...genKeywords(KEYWORDS, monaco.languages.CompletionItemKind.Keyword, range), 40 | { 41 | label: 'chiều dài mảng', 42 | kind: monaco.languages.CompletionItemKind.Function, 43 | insertText: 'chiều dài mảng', 44 | range 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /playground/src/editor/config.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor-core' 2 | 3 | export const languageID = 'Vietscript' 4 | 5 | export const languageExtensionPoint: monaco.languages.ILanguageExtensionPoint = { 6 | id: languageID 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/guard/AdminGuard.tsx: -------------------------------------------------------------------------------- 1 | import { Alert } from 'antd' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | interface Props { 5 | projectID?: string 6 | havePermission?: boolean 7 | permissionMessage?: string 8 | } 9 | 10 | function AdminGuard({ 11 | children, 12 | havePermission = true, 13 | permissionMessage = "You don't have permission to view it" 14 | }: PropsWithChildren) { 15 | if (!havePermission) { 16 | return 17 | } 18 | return <>{children} 19 | } 20 | 21 | export default AdminGuard 22 | -------------------------------------------------------------------------------- /playground/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | /* For Webkit-based browsers (Chrome, Safari and Opera) */ 7 | .scrollbar-hide::-webkit-scrollbar { 8 | display: none; 9 | } 10 | 11 | /* For IE, Edge and Firefox */ 12 | .scrollbar-hide { 13 | -ms-overflow-style: none; 14 | /* IE and Edge */ 15 | scrollbar-width: none; 16 | /* Firefox */ 17 | } 18 | 19 | * { 20 | margin: 0; 21 | padding: 0; 22 | box-sizing: border-box; 23 | } 24 | 25 | a { 26 | color: #fff; 27 | text-decoration: none; 28 | } 29 | 30 | button { 31 | outline: none; 32 | box-sizing: border-box; 33 | } -------------------------------------------------------------------------------- /playground/src/interface/app.ts: -------------------------------------------------------------------------------- 1 | import { UploadProps } from 'antd' 2 | import { iteratee } from 'lodash' 3 | 4 | export interface ReactWithChild { 5 | children?: React.ReactNode 6 | } 7 | 8 | export type Dictionary = Record 9 | 10 | export type ValidValue = Exclude 11 | 12 | export const BooleanFilter = (x: T): x is ValidValue => Boolean(x) 13 | export type LazyLoadElement = () => Promise<{ default: React.ComponentType }> 14 | 15 | export interface Route { 16 | path: string 17 | element: LazyLoadElement 18 | children?: Route[] 19 | } 20 | 21 | export interface UploadRef extends UploadProps { 22 | onReset: () => void 23 | imageUrl: string | null 24 | setImageUrl: (url: string) => void 25 | } 26 | 27 | export interface Profile { 28 | user_name: string 29 | email: string 30 | id: string 31 | created_at: string 32 | } 33 | -------------------------------------------------------------------------------- /playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import { QueryClient, QueryClientProvider } from "react-query"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import App from "./App"; 5 | import "./index.css"; 6 | 7 | import { loader } from "@monaco-editor/react"; 8 | 9 | import * as monaco from "monaco-editor"; 10 | import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; 11 | import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; 12 | import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; 13 | import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; 14 | import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; 15 | 16 | const queryClient = new QueryClient({ 17 | defaultOptions: { 18 | queries: { 19 | refetchOnWindowFocus: false, 20 | }, 21 | }, 22 | }); 23 | 24 | self.MonacoEnvironment = { 25 | getWorker(_, label) { 26 | if (label === "json") { 27 | return new jsonWorker(); 28 | } 29 | if (label === "css" || label === "scss" || label === "less") { 30 | return new cssWorker(); 31 | } 32 | if (label === "html" || label === "handlebars" || label === "razor") { 33 | return new htmlWorker(); 34 | } 35 | if (label === "typescript" || label === "javascript") { 36 | return new tsWorker(); 37 | } 38 | return new editorWorker(); 39 | }, 40 | }; 41 | 42 | loader.config({ monaco }); 43 | 44 | loader.init().then((res) => { 45 | console.log(res); 46 | }); 47 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 48 | 49 | 50 | 51 | 52 | , 53 | ); 54 | -------------------------------------------------------------------------------- /playground/src/pages/Dev.tsx: -------------------------------------------------------------------------------- 1 | import { CaretRightOutlined } from "@ant-design/icons"; 2 | import Editor, { useMonaco } from "@monaco-editor/react"; 3 | import { Button, Card, Col, Row } from "antd"; 4 | import { useEffect, useState } from "react"; 5 | import { Link } from "react-router-dom"; 6 | import "react-toastify/dist/ReactToastify.css"; 7 | import { createDependencyProposals } from "src/editor/autocomplete"; 8 | import { languageExtensionPoint, languageID } from "src/editor/config"; 9 | import { monarchLanguage, richLanguageConfiguration } from "src/editor/vietscript"; 10 | 11 | function Dev() { 12 | const [program, setProgram] = useState(""); 13 | const [result, setResult] = useState(""); 14 | const monaco = useMonaco(); 15 | 16 | function handleEditorChange(value: any) { 17 | setProgram(value); 18 | } 19 | 20 | const executeCode = () => { 21 | let capturedOutput = ""; 22 | const originalConsoleLog = console.log; 23 | 24 | console.log = (output) => { 25 | capturedOutput += output; 26 | }; 27 | 28 | try { 29 | // const _program = transpiler.compile(program) 30 | const _program = { target: program }; 31 | eval(_program.target); 32 | setResult(capturedOutput); 33 | } catch (error) { 34 | setResult(`Lỗi: ${error}`); 35 | } finally { 36 | console.log = originalConsoleLog; 37 | } 38 | }; 39 | 40 | useEffect(() => { 41 | // do conditional chaining 42 | monaco?.languages.typescript.javascriptDefaults.setEagerModelSync(true); 43 | monaco?.languages.register(languageExtensionPoint); 44 | monaco?.languages.onLanguage(languageID, () => { 45 | monaco?.languages.setMonarchTokensProvider(languageID, monarchLanguage); 46 | monaco?.languages.setLanguageConfiguration(languageID, richLanguageConfiguration); 47 | }); 48 | 49 | monaco?.languages.registerCompletionItemProvider(languageID, { 50 | provideCompletionItems: function (model, position) { 51 | const word = model.getWordUntilPosition(position); 52 | const range = { 53 | startLineNumber: position.lineNumber, 54 | endLineNumber: position.lineNumber, 55 | startColumn: word.startColumn, 56 | endColumn: word.endColumn, 57 | }; 58 | return { 59 | suggestions: createDependencyProposals(range, monaco), 60 | }; 61 | }, 62 | }); 63 | 64 | if (monaco) { 65 | console.log("here is the monaco instance:", monaco); 66 | } 67 | }, [monaco]); 68 | 69 | return ( 70 | 71 |
72 | 73 | 74 | 75 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |

Kết quả

87 |
88 | <>{result ? JSON.parse(JSON.stringify(result)) : 'Nhấn nút "thực thi" để biên dịch code'} 89 |
90 | 91 |
92 |
93 | ); 94 | } 95 | 96 | export default Dev; 97 | -------------------------------------------------------------------------------- /playground/src/pages/NotFound.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFoundPage() { 2 | return ( 3 |
4 |

Page Not Found

5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/routes/DefaultRoutes.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom' 2 | import DefaultLayout from 'src/components/layouts/DefaultLayout' 3 | 4 | function DefaultRoute() { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | 12 | export default DefaultRoute 13 | -------------------------------------------------------------------------------- /playground/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type TestCase = { 2 | input: string; 3 | expectedOutput: string; 4 | }; 5 | 6 | export type Result = TestCase & { 7 | result: string; 8 | }; 9 | -------------------------------------------------------------------------------- /playground/src/useRoutesElement.tsx: -------------------------------------------------------------------------------- 1 | import { RouteObject, useRoutes } from "react-router-dom"; 2 | 3 | // component 4 | import { Row, Spin } from "antd"; 5 | import { Suspense, lazy } from "react"; 6 | import { Route } from "./interface/app"; 7 | import NotFoundPage from "./pages/NotFound"; 8 | import DefaultRoute from "./routes/DefaultRoutes"; 9 | import Dev from "./pages/Dev"; 10 | 11 | export const DEFAULT_ROUTE: Route[] = [ 12 | { 13 | path: "", 14 | element: () => import("src/pages/Home"), 15 | }, 16 | ]; 17 | interface RouteElement { 18 | routeElement: () => Promise; 19 | isPrivate?: boolean; 20 | } 21 | interface LazyRouteProps { 22 | routes: Route[]; 23 | } 24 | function LazyElement({ routeElement }: RouteElement) { 25 | const LazyComponent = lazy(routeElement); 26 | return ( 27 | 30 | 31 | 32 | } 33 | > 34 | 35 | 36 | ); 37 | } 38 | function wrapRoutesWithLazy({ routes }: LazyRouteProps): RouteObject[] { 39 | return routes?.map((route: Route) => ({ 40 | path: route.path, 41 | element: , 42 | ...(route.children && { children: wrapRoutesWithLazy({ routes: route.children }) }), 43 | })); 44 | } 45 | export default function useRouteElements() { 46 | const routeElements = [ 47 | { 48 | path: "*", 49 | element: , 50 | }, 51 | { 52 | path: "/dev-mode", 53 | element: , 54 | }, 55 | { 56 | path: "/", 57 | element: , 58 | children: wrapRoutesWithLazy({ routes: DEFAULT_ROUTE }), 59 | }, 60 | ]; 61 | return useRoutes(routeElements); 62 | } 63 | -------------------------------------------------------------------------------- /playground/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const formatFunctionName = (name: string) => 2 | `_${name.replace(/(\s)|(^[0-9]+)|([^\sA-Za-z])/g, (_, p1, p2, p3) => { 3 | if (p1) return "_"; 4 | else if (p2) return "_" + p2; 5 | else return String(p3.codePointAt(0)); 6 | })}`; 7 | -------------------------------------------------------------------------------- /playground/src/utils/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | 3 | export const supabase = createClient( 4 | "https://inkryqrjlvcrdegmzhwi.supabase.co", 5 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imlua3J5cXJqbHZjcmRlZ216aHdpIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTU2OTM2MDMsImV4cCI6MjAzMTI2OTYwM30.yRNvV1M8deeQbJf1dZXBlLSla22G0TC7c6gux8qCVlw", 6 | ); 7 | -------------------------------------------------------------------------------- /playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: { 6 | maxHeight: { 7 | 30: '120px' 8 | } 9 | } 10 | }, 11 | plugins: [], 12 | corePlugins: { 13 | preflight: false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "baseUrl": "." 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unresolved 2 | import { defineConfig } from 'vitest/config' 3 | import react from '@vitejs/plugin-react' 4 | import path from 'path' 5 | import { visualizer } from 'rollup-plugin-visualizer' 6 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [react(), visualizer(), nodePolyfills()] as any, 11 | define: {}, 12 | test: { 13 | environment: 'jsdom', 14 | setupFiles: path.resolve(__dirname, './vitest.setup.js') 15 | }, 16 | server: { 17 | port: 3001 18 | }, 19 | css: { 20 | devSourcemap: true 21 | }, 22 | resolve: { 23 | alias: { 24 | src: path.resolve(__dirname, './src') 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "sandbox" 4 | - "playground" 5 | -------------------------------------------------------------------------------- /sandbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vietscript/sandbox", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "typecheck": "tsc --noEmit", 7 | "build": "rimraf ./dist && vite build" 8 | }, 9 | "dependencies": { 10 | "@vietscript/plugin-vite": "workspace:*", 11 | "@vietscript/parser": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "@vitest/ui": "^0.24.5", 15 | "rimraf": "^3.0.2", 16 | "typescript": "^4.9.4", 17 | "vite": "^4.0.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sandbox/src/index.vjs: -------------------------------------------------------------------------------- 1 | lớp Con Mèo (Động Vật) { 2 | nói () { 3 | trả về "Meo meo"; 4 | } 5 | } -------------------------------------------------------------------------------- /sandbox/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "outDir": "./dist", 5 | "target": "ESNext", 6 | "useDefineForClassFields": true, 7 | "module": "ESNext", 8 | "moduleResolution": "Node", 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "esModuleInterop": true, 14 | "skipLibCheck": true, 15 | "lib": ["ESNext", "DOM"], 16 | "types": ["vite/client"] 17 | }, 18 | "include": ["./**/*.ts", "./**/*.d.ts", "./**/*.tsx", "./**/*.vue"], 19 | "references": [{ "path": "./tsconfig.node.json" }] 20 | } 21 | -------------------------------------------------------------------------------- /sandbox/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /sandbox/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import VietScriptPlugin from "@vietscript/plugin-vite"; 4 | import { defineConfig } from "vite"; 5 | 6 | export default defineConfig({ 7 | build: { 8 | emptyOutDir: false, 9 | lib: { 10 | name: "vietscript", 11 | entry: path.resolve("./src/index.vjs"), 12 | formats: ["es"], 13 | fileName: "index.mjs", 14 | }, 15 | }, 16 | plugins: [VietScriptPlugin()], 17 | }); 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "allowJs": true, 6 | "allowSyntheticDefaultImports": true, 7 | "jsx": "preserve", 8 | "module": "ESNext", 9 | "moduleResolution": "Node", 10 | "target": "ESNext", 11 | "declaration": false, 12 | "sourceMap": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": true, 15 | "skipLibCheck": true, 16 | "strictNullChecks": true, 17 | "useDefineForClassFields": true, 18 | "lib": ["ESNext", "DOM"], 19 | "types": ["node", "vitest/globals"], 20 | "outDir": "./dist" 21 | }, 22 | "include": ["./**/*.ts", "./**/*.d.ts", "./**/*.test.ts"], 23 | "exclude": ["./node_modules", "./dist"] 24 | } 25 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { defineConfig } from "vitest/config"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import vueJsx from "@vitejs/plugin-vue-jsx"; 6 | 7 | export default defineConfig({ 8 | plugins: [vue(), vueJsx()], 9 | resolve: { 10 | alias: { 11 | "@parser": path.resolve("./packages/parser/src"), 12 | }, 13 | }, 14 | test: { 15 | globals: true, 16 | environment: "jsdom", 17 | testTimeout: 10_000, 18 | }, 19 | }); 20 | --------------------------------------------------------------------------------