├── .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 |
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 |
--------------------------------------------------------------------------------