diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml deleted file mode 100644 index 471d51ee..00000000 --- a/.github/workflows/cla.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: "CLA Assistant" -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened,closed,synchronize] - -permissions: - actions: write - contents: write - pull-requests: write - statuses: write - -jobs: - CLAAssistant: - runs-on: ubuntu-latest - steps: - - name: "CLA Assistant" - if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - uses: contributor-assistant/github-action@v2.3.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - with: - path-to-signatures: 'signatures.json' - path-to-document: 'https://github.com/Stepami/extended-js-subset/blob/master/contributor-licence-agreement.md' - allowlist: Stepami,bot* - branch: 'main' - remote-repository-name: hydrascript-cla-signatures \ No newline at end of file diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 3d9d44dd..972f9188 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -2,68 +2,89 @@ name: Develop Workflow on: push: - branches: - - '**' - pull_request: - branches: - - '**' + branches-ignore: + - 'release_test' + pull_request_target: + branches-ignore: + - 'release_test' + +permissions: + actions: write + checks: write + contents: write + issues: write + pull-requests: write jobs: build-and-test: name: Build & Test - runs-on: ubuntu-latest + runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - - name: Cache NuGet packages - uses: actions/cache@v3 + dotnet-version: 8.0.x + - name: Setup GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj*') }} + versionSpec: '5.12.0' + - name: Determine Version + id: version_step + uses: gittools/actions/gitversion/execute@v3.0.0 + with: + useConfigFile: true + - name: Push New Version Tag + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + run: | + $gv = 'v${{ steps.version_step.outputs.majorMinorPatch }}' #gitversion result + $lt = $( git describe --tags --abbrev=0 ) # last tag version result + if ( -Not ( $gv -eq $lt ) ) + { + git tag $gv + git push origin --tags + } - name: Restore dependencies run: dotnet restore - name: Build - run: dotnet build --no-restore -c Release -v n - - name: Test + run: dotnet build /p:Version=${{ steps.version_step.outputs.fullSemVer }} --no-restore -c Release -v n + - name: Unit Tests run: | - dotnet test -c Release --no-build -v n --filter="Category=Unit" + dotnet test -c Release ./tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj --no-build -v n + dotnet test -c Release /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura --no-build -v n --filter="Category=Unit" mkdir coverage-report - name: Code Coverage Summary Report For Merge Request - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' uses: 5monkeys/cobertura-action@master with: - path: ./Interpreter.Tests/coverage.cobertura.xml + path: ./tests/HydraScript.Tests/coverage.cobertura.xml repo_token: ${{ secrets.GITHUB_TOKEN }} - minimum_coverage: 80 + minimum_coverage: 20 fail_below_threshold: true show_class_names: true show_missing: true link_missing_lines: true show_branch: true only_changed_files: true - - name: Code Coverage Summary Report For Master - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - uses: irongut/CodeCoverageSummary@v1.3.0 - with: - filename: ./Interpreter.Tests/coverage.cobertura.xml - badge: true - fail_below_min: false - format: markdown - hide_branch_rate: true - hide_complexity: true - thresholds: '80 100' - name: ReportGenerator - uses: danielpalme/ReportGenerator-GitHub-Action@5.1.10 + uses: danielpalme/ReportGenerator-GitHub-Action@5.2.1 with: - reports: './Interpreter.Tests/coverage.cobertura.xml' + reports: './tests/HydraScript.Tests/coverage.cobertura.xml' targetdir: './coverage-report' - name: Upload coverage report artifact if: github.event_name == 'push' - uses: actions/upload-artifact@v2.2.3 + uses: actions/upload-artifact@v4 with: name: CoverageReport path: coverage-report + - name: Integration Tests + run: dotnet test -c Release --no-build -v n --filter="Category=Integration" + - name: Upload Windows Build + if: github.ref != 'refs/heads/master' && github.event_name == 'push' + uses: actions/upload-artifact@v4 + with: + name: windows_build_${{ steps.version_step.outputs.fullSemVer }} + path: ./src/HydraScript/bin/Release/net8.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f16a1013..8b56d333 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,57 +2,105 @@ name: Release Workflow on: push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' + branches: + - 'release_test' jobs: create-release: name: Create release runs-on: ubuntu-latest - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} + outputs: + determined_version: ${{ steps.version_step.outputs.majorMinorPatch }} steps: - name: Checkout - uses: actions/checkout@v3 - - name: Create release - id: create_release - uses: ncipollo/release-action@v1 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: '5.12.0' + - name: Determine Version + id: version_step + uses: gittools/actions/gitversion/execute@v3.0.0 + with: + useConfigFile: true + - name: Setup GitReleaseManager + uses: gittools/actions/gitreleasemanager/setup@v3.0.0 + with: + versionSpec: '0.18.x' + - name: Create release with GitReleaseManager + uses: gittools/actions/gitreleasemanager/create@v3.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} - + owner: 'Stepami' + repository: 'hydrascript' + milestone: 'v${{ steps.version_step.outputs.majorMinorPatch }}' + name: 'v${{ steps.version_step.outputs.majorMinorPatch }}' + upload-release-assets: name: Upload release assets needs: create-release + outputs: + determined_version: ${{ needs.create-release.outputs.determined_version }} strategy: matrix: config: - os: ubuntu-latest rid: linux-x64 - type: application/x-pie-executable - - os: macos-latest + - os: macos-12 rid: osx-x64 - type: application/x-mach-binary + - os: macos-14 + rid: osx-arm64 - os: windows-latest rid: win-x64 - type: application/x-dosexec runs-on: ${{ matrix.config.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x + - name: Setup GitReleaseManager + uses: gittools/actions/gitreleasemanager/setup@v3.0.0 + with: + versionSpec: '0.18.x' - name: Publish run: | mkdir output - dotnet publish ./Interpreter/Interpreter.csproj -c Release -r ${{ matrix.config.rid }} -p:PublishSingleFile=true -p:DebugType=embedded --self-contained false -o ./output - - name: Upload release assets - uses: shogo82148/actions-upload-release-asset@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_name: interpreter-${{ matrix.config.rid }}${{ matrix.config.rid == 'win-x64' && '.exe' || '' }} - asset_path: ./output/Interpreter${{ matrix.config.rid == 'win-x64' && '.exe' || '' }} - asset_content_type: ${{ matrix.config.type }} + dotnet publish ./src/HydraScript/HydraScript.csproj -c Release -r ${{ matrix.config.rid }} -p:PublishSingleFile=true -p:DebugType=embedded -p:Version=${{ needs.create-release.outputs.determined_version }} --self-contained false -o ./output + - name: Rename Executable + run: mv ./output/HydraScript${{ matrix.config.rid == 'win-x64' && '.exe' || '' }} ./output/hydrascript_${{ matrix.config.rid }}${{ matrix.config.rid == 'win-x64' && '.exe' || '' }} + - name: Add asset to a release with GitReleaseManager + uses: gittools/actions/gitreleasemanager/addasset@v3.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + owner: 'Stepami' + repository: 'hydrascript' + milestone: 'v${{ needs.create-release.outputs.determined_version }}' + assets: ./output/hydrascript_${{ matrix.config.rid }}${{ matrix.config.rid == 'win-x64' && '.exe' || '' }} + publish-release: + name: Publish release + runs-on: ubuntu-latest + needs: upload-release-assets + steps: + - name: Setup GitReleaseManager + uses: gittools/actions/gitreleasemanager/setup@v3.0.0 + with: + versionSpec: '0.18.x' + - name: Publish release with GitReleaseManager + uses: gittools/actions/gitreleasemanager/publish@v3.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + owner: 'Stepami' + repository: 'hydrascript' + milestone: 'v${{ needs.upload-release-assets.outputs.determined_version }}' + - name: Close release with GitReleaseManager + uses: gittools/actions/gitreleasemanager/close@v3.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + owner: 'Stepami' + repository: 'hydrascript' + milestone: 'v${{ needs.upload-release-assets.outputs.determined_version }}' \ No newline at end of file diff --git a/.gitignore b/.gitignore index c77b3da7..629f5fff 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ _UpgradeReport_Files [Pp]ackages +tests/**/coverage.cobertura.xml + Thumbs.db Desktop.ini .DS_Store \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e67a471..0908662d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Contributions are welcomed! Here's a few things to know: ## Steps to Contributing Here are the basic steps to get started with your first contribution. Please reach out with any questions. -1. Use [open issues](https://github.com/stepami/extended-js-subset/issues) to discuss the proposed changes. Create an issue describing changes if necessary to collect feedback. Also, please use provided labels to tag issues so everyone can easily sort issues of interest. +1. Use [open issues](https://github.com/stepami/hydrascript/issues) to discuss the proposed changes. Create an issue describing changes if necessary to collect feedback. Also, please use provided labels to tag issues so everyone can easily sort issues of interest. 1. [Fork the repo](https://help.github.com/articles/fork-a-repo/) in order if you want to make and test local changes. 1. Create a new branch **from master** for the issue. We suggest prefixing the branch with type of contribution (`bugfix`/`feature`), your username and then a descriptive title: (e.g. `bugfix/user1/object-comparision` or `feature/user2/variable-initialization-check`) 1. Make code changes. diff --git a/ExtendedJavaScriptSubset.sln b/ExtendedJavaScriptSubset.sln index b37528cd..3a42a319 100644 --- a/ExtendedJavaScriptSubset.sln +++ b/ExtendedJavaScriptSubset.sln @@ -1,10 +1,97 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interpreter.Lib", "Interpreter.Lib\Interpreter.Lib.csproj", "{83524079-4A56-4AF0-9011-F7CA871536E9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript", "src\HydraScript\HydraScript.csproj", "{5DA79C0C-2B98-4E64-81EA-92AFFC5204D3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interpreter", "Interpreter\Interpreter.csproj", "{5DA79C0C-2B98-4E64-81EA-92AFFC5204D3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Tests", "tests\HydraScript.Tests\HydraScript.Tests.csproj", "{F0DD56CB-C68A-4CDC-AA54-0A07342FE08B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interpreter.Tests", "Interpreter.Tests\Interpreter.Tests.csproj", "{F0DD56CB-C68A-4CDC-AA54-0A07342FE08B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{86CF2A2F-4DFE-48E7-B062-EF824730916A}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE = LICENSE + Readme.md = Readme.md + SECURITY.md = SECURITY.md + hydrascript-logo.jpg = hydrascript-logo.jpg + GitVersion.yml = GitVersion.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GithubFolder", "GithubFolder", "{54CBE5A7-3C3E-44ED-B877-7B08A818083B}" + ProjectSection(SolutionItems) = preProject + .github\pull_request_template.md = .github\pull_request_template.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{C7773DC6-9052-4F69-B947-CBFD5D663E80}" + ProjectSection(SolutionItems) = preProject + .github\workflows\develop.yml = .github\workflows\develop.yml + .github\workflows\release.yml = .github\workflows\release.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplate", "IssueTemplate", "{0E31199C-4893-4920-A356-D1FF7850BFAD}" + ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\bug_report.md = .github\ISSUE_TEMPLATE\bug_report.md + .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{04ABBF20-AD95-4366-8EDE-57095A37735E}" + ProjectSection(SolutionItems) = preProject + samples\abs.js = samples\abs.js + samples\arraddremove.js = samples\arraddremove.js + samples\arreditread.js = samples\arreditread.js + samples\ceil.js = samples\ceil.js + samples\defaultarray.js = samples\defaultarray.js + samples\equals.js = samples\equals.js + samples\exprtest.js = samples\exprtest.js + samples\fastpow.js = samples\fastpow.js + samples\forwardref.js = samples\forwardref.js + samples\gcd.js = samples\gcd.js + samples\lcm.js = samples\lcm.js + samples\linkedlist.js = samples\linkedlist.js + samples\objeditread.js = samples\objeditread.js + samples\posneg.js = samples\posneg.js + samples\prime.js = samples\prime.js + samples\primefactor.js = samples\primefactor.js + samples\quicksort.js = samples\quicksort.js + samples\range.js = samples\range.js + samples\recur.js = samples\recur.js + samples\searchinll.js = samples\searchinll.js + samples\settable.js = samples\settable.js + samples\squareroot.js = samples\squareroot.js + samples\summator.js = samples\summator.js + samples\tern.js = samples\tern.js + samples\this.js = samples\this.js + samples\typeresolving.js = samples\typeresolving.js + samples\vec2d.js = samples\vec2d.js + samples\cycled.js = samples\cycled.js + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{FB8F6EE1-1942-46D6-954E-9A1647BBDF10}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3F131901-A9EC-451A-B7E9-726887CFE5FB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{A280C99E-0112-4F3C-A5E9-B793F2D9898E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{F0FC6D23-F932-4C11-8D0F-07894FC61511}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{B7DDF6C9-B67C-430A-948A-A380EF68DEF1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Domain.FrontEnd", "src\Domain\HydraScript.Domain.FrontEnd\HydraScript.Domain.FrontEnd.csproj", "{B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Domain.IR", "src\Domain\HydraScript.Domain.IR\HydraScript.Domain.IR.csproj", "{BB913678-CB63-43BA-9C78-40D9A1FE85D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Domain.BackEnd", "src\Domain\HydraScript.Domain.BackEnd\HydraScript.Domain.BackEnd.csproj", "{2B32BBCC-4562-4253-B85B-80E8A278551F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Application.StaticAnalysis", "src\Application\HydraScript.Application.StaticAnalysis\HydraScript.Application.StaticAnalysis.csproj", "{01376DD7-A13C-476C-A8DB-44EA9BB3F64F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Application.CodeGeneration", "src\Application\HydraScript.Application.CodeGeneration\HydraScript.Application.CodeGeneration.csproj", "{07DEA726-9772-4B61-AA66-1B82823F6BF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Infrastructure", "src\Infrastructure\HydraScript.Infrastructure\HydraScript.Infrastructure.csproj", "{160B77ED-BA30-40A2-81EF-F5D1E4D22039}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.IntegrationTests", "tests\HydraScript.IntegrationTests\HydraScript.IntegrationTests.csproj", "{1CE98127-3027-4BD4-AAA3-63A589B09E73}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Infrastructure.LexerRegexGenerator", "src\Infrastructure\HydraScript.Infrastructure.LexerRegexGenerator\HydraScript.Infrastructure.LexerRegexGenerator.csproj", "{74D1495B-12A4-4E1A-ABE0-93029ECDC5FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HydraScript.Infrastructure.LexerRegexGenerator.Tests", "tests\HydraScript.Infrastructure.LexerRegexGenerator.Tests\HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj", "{829111AD-4A5C-4B3D-AC28-208309CE10D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,10 +99,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {83524079-4A56-4AF0-9011-F7CA871536E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83524079-4A56-4AF0-9011-F7CA871536E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83524079-4A56-4AF0-9011-F7CA871536E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83524079-4A56-4AF0-9011-F7CA871536E9}.Release|Any CPU.Build.0 = Release|Any CPU {5DA79C0C-2B98-4E64-81EA-92AFFC5204D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5DA79C0C-2B98-4E64-81EA-92AFFC5204D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DA79C0C-2B98-4E64-81EA-92AFFC5204D3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -24,5 +107,60 @@ Global {F0DD56CB-C68A-4CDC-AA54-0A07342FE08B}.Debug|Any CPU.Build.0 = Debug|Any CPU {F0DD56CB-C68A-4CDC-AA54-0A07342FE08B}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0DD56CB-C68A-4CDC-AA54-0A07342FE08B}.Release|Any CPU.Build.0 = Release|Any CPU + {B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD}.Release|Any CPU.Build.0 = Release|Any CPU + {BB913678-CB63-43BA-9C78-40D9A1FE85D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB913678-CB63-43BA-9C78-40D9A1FE85D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB913678-CB63-43BA-9C78-40D9A1FE85D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB913678-CB63-43BA-9C78-40D9A1FE85D9}.Release|Any CPU.Build.0 = Release|Any CPU + {2B32BBCC-4562-4253-B85B-80E8A278551F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B32BBCC-4562-4253-B85B-80E8A278551F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B32BBCC-4562-4253-B85B-80E8A278551F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B32BBCC-4562-4253-B85B-80E8A278551F}.Release|Any CPU.Build.0 = Release|Any CPU + {01376DD7-A13C-476C-A8DB-44EA9BB3F64F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01376DD7-A13C-476C-A8DB-44EA9BB3F64F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01376DD7-A13C-476C-A8DB-44EA9BB3F64F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01376DD7-A13C-476C-A8DB-44EA9BB3F64F}.Release|Any CPU.Build.0 = Release|Any CPU + {07DEA726-9772-4B61-AA66-1B82823F6BF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07DEA726-9772-4B61-AA66-1B82823F6BF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07DEA726-9772-4B61-AA66-1B82823F6BF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07DEA726-9772-4B61-AA66-1B82823F6BF0}.Release|Any CPU.Build.0 = Release|Any CPU + {160B77ED-BA30-40A2-81EF-F5D1E4D22039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {160B77ED-BA30-40A2-81EF-F5D1E4D22039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {160B77ED-BA30-40A2-81EF-F5D1E4D22039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {160B77ED-BA30-40A2-81EF-F5D1E4D22039}.Release|Any CPU.Build.0 = Release|Any CPU + {1CE98127-3027-4BD4-AAA3-63A589B09E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CE98127-3027-4BD4-AAA3-63A589B09E73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CE98127-3027-4BD4-AAA3-63A589B09E73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CE98127-3027-4BD4-AAA3-63A589B09E73}.Release|Any CPU.Build.0 = Release|Any CPU + {74D1495B-12A4-4E1A-ABE0-93029ECDC5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74D1495B-12A4-4E1A-ABE0-93029ECDC5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74D1495B-12A4-4E1A-ABE0-93029ECDC5FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74D1495B-12A4-4E1A-ABE0-93029ECDC5FE}.Release|Any CPU.Build.0 = Release|Any CPU + {829111AD-4A5C-4B3D-AC28-208309CE10D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {829111AD-4A5C-4B3D-AC28-208309CE10D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {829111AD-4A5C-4B3D-AC28-208309CE10D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {829111AD-4A5C-4B3D-AC28-208309CE10D6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {54CBE5A7-3C3E-44ED-B877-7B08A818083B} = {86CF2A2F-4DFE-48E7-B062-EF824730916A} + {C7773DC6-9052-4F69-B947-CBFD5D663E80} = {54CBE5A7-3C3E-44ED-B877-7B08A818083B} + {0E31199C-4893-4920-A356-D1FF7850BFAD} = {54CBE5A7-3C3E-44ED-B877-7B08A818083B} + {5DA79C0C-2B98-4E64-81EA-92AFFC5204D3} = {FB8F6EE1-1942-46D6-954E-9A1647BBDF10} + {F0DD56CB-C68A-4CDC-AA54-0A07342FE08B} = {3F131901-A9EC-451A-B7E9-726887CFE5FB} + {A280C99E-0112-4F3C-A5E9-B793F2D9898E} = {FB8F6EE1-1942-46D6-954E-9A1647BBDF10} + {F0FC6D23-F932-4C11-8D0F-07894FC61511} = {FB8F6EE1-1942-46D6-954E-9A1647BBDF10} + {B7DDF6C9-B67C-430A-948A-A380EF68DEF1} = {FB8F6EE1-1942-46D6-954E-9A1647BBDF10} + {B7FD15B2-A3C4-4A17-B6DE-CC87E35DFCCD} = {F0FC6D23-F932-4C11-8D0F-07894FC61511} + {BB913678-CB63-43BA-9C78-40D9A1FE85D9} = {F0FC6D23-F932-4C11-8D0F-07894FC61511} + {2B32BBCC-4562-4253-B85B-80E8A278551F} = {F0FC6D23-F932-4C11-8D0F-07894FC61511} + {01376DD7-A13C-476C-A8DB-44EA9BB3F64F} = {A280C99E-0112-4F3C-A5E9-B793F2D9898E} + {07DEA726-9772-4B61-AA66-1B82823F6BF0} = {A280C99E-0112-4F3C-A5E9-B793F2D9898E} + {160B77ED-BA30-40A2-81EF-F5D1E4D22039} = {B7DDF6C9-B67C-430A-948A-A380EF68DEF1} + {1CE98127-3027-4BD4-AAA3-63A589B09E73} = {3F131901-A9EC-451A-B7E9-726887CFE5FB} + {74D1495B-12A4-4E1A-ABE0-93029ECDC5FE} = {B7DDF6C9-B67C-430A-948A-A380EF68DEF1} + {829111AD-4A5C-4B3D-AC28-208309CE10D6} = {3F131901-A9EC-451A-B7E9-726887CFE5FB} EndGlobalSection EndGlobal diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..d53da919 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,7 @@ +mode: Mainline +branches: + release: + increment: None +ignore: + sha: [] +merge-message-formats: {} diff --git a/Interpreter.Lib/BackEnd/Instructions/AsString.cs b/Interpreter.Lib/BackEnd/Instructions/AsString.cs deleted file mode 100644 index 333eca74..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/AsString.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Serialization; -using Interpreter.Lib.BackEnd.Values; -using SystemType = System.Type; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class AsString : Simple -{ - public AsString(string left, IValue right, int number) : - base(left, (null, right), "", number) - { - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - frame[Left] = JsonSerializer.Serialize( - right.right.Get(frame), - new JsonSerializerOptions - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - ReferenceHandler = ReferenceHandler.IgnoreCycles, - Converters = { new DoubleValueWriteConverter() }, - NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals - } - ); - - return Jump(); - } - - protected override string ToStringRepresentation() => $"{Left} = {right.right} as string"; - - [ExcludeFromCodeCoverage] - private class DoubleValueWriteConverter : JsonConverter - { - public override double Read(ref Utf8JsonReader reader, - SystemType typeToConvert, JsonSerializerOptions options) => - throw new NotImplementedException(); - - public override void Write(Utf8JsonWriter writer, - double value, JsonSerializerOptions options) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (value == Math.Truncate(value)) - writer.WriteNumberValue(Convert.ToInt64(value)); - else - writer.WriteNumberValue(value); - } - } -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/BeginFunction.cs b/Interpreter.Lib/BackEnd/Instructions/BeginFunction.cs deleted file mode 100644 index 861a8760..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/BeginFunction.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class BeginFunction : Instruction -{ - private readonly FunctionInfo _function; - - public BeginFunction(int number, FunctionInfo function) : base(number) - { - _function = function; - } - - public override int Execute(VirtualMachine vm) => Number + 1; - - protected override string ToStringRepresentation() => $"BeginFunction {_function.CallId()}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/CallFunction.cs b/Interpreter.Lib/BackEnd/Instructions/CallFunction.cs deleted file mode 100644 index 30f5a128..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/CallFunction.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class CallFunction : Simple -{ - private readonly FunctionInfo _function; - private readonly int _numberOfArguments; - - public CallFunction(FunctionInfo function, int number, int numberOfArguments, string left = null) : - base(left, (null, null), "Call ", number) - { - _function = function; - _numberOfArguments = numberOfArguments + Convert.ToInt32(function.MethodOf != null); - } - - public override int Jump() => _function.Location; - - public override int Execute(VirtualMachine vm) - { - var frame = new Frame(Number + 1, vm.Frames.Peek()); - - var i = 0; - var args = new List<(string Id, object Value)>(); - while (i < _numberOfArguments) - { - args.Add(vm.Arguments.Pop()); - frame[args[i].Id] = args[i].Value; - i++; - } - - if (_function.MethodOf != null) - { - var obj = (Dictionary) frame[_function.MethodOf]; - foreach (var (key, value) in obj) - { - frame[key] = value; - } - } - - vm.CallStack.Push(new Call(Number, _function, args, Left)); - vm.Frames.Push(frame); - return _function.Location; - } - - protected override string ToStringRepresentation() => Left == null - ? $"Call {_function}, {_numberOfArguments}" - : $"{Left} = Call {_function}, {_numberOfArguments}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/CreateArray.cs b/Interpreter.Lib/BackEnd/Instructions/CreateArray.cs deleted file mode 100644 index 6dbf2330..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/CreateArray.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class CreateArray : Instruction -{ - private readonly string _id; - private readonly int _size; - - public CreateArray(int number, string id, int size) : base(number) - { - _id = id; - _size = size; - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - frame[_id] = new object[_size].ToList(); - return Number + 1; - } - - protected override string ToStringRepresentation() => $"array {_id} = [{_size}]"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/CreateObject.cs b/Interpreter.Lib/BackEnd/Instructions/CreateObject.cs deleted file mode 100644 index 973c5be3..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/CreateObject.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class CreateObject : Instruction -{ - private readonly string _id; - - public CreateObject(int number, string id) : base(number) - { - _id = id; - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - frame[_id] = new Dictionary(); - return Number + 1; - } - - protected override string ToStringRepresentation() => $"object {_id} = {{}}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/DotAssignment.cs b/Interpreter.Lib/BackEnd/Instructions/DotAssignment.cs deleted file mode 100644 index 8aaf66b0..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/DotAssignment.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class DotAssignment : Simple -{ - public DotAssignment(string left, (IValue left, IValue right) right, int number) : - base(left, right, ".", number) - { - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - var obj = (Dictionary) frame[Left]; - var field = (string) right.left.Get(frame) ?? string.Empty; - obj[field] = right.right.Get(frame); - return Number + 1; - } - - protected override string ToStringRepresentation() => - $"{Left}{@operator}{right.left} = {right.right}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Goto.cs b/Interpreter.Lib/BackEnd/Instructions/Goto.cs deleted file mode 100644 index e633e7e3..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Goto.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class Goto : Instruction -{ - protected int jump; - - public Goto(int jump, int number) : base(number) - { - this.jump = jump; - } - - public override int Jump() => jump; - - public override int Execute(VirtualMachine vm) => Jump(); - - public void SetJump(int newJump) => jump = newJump; - - protected override string ToStringRepresentation() => $"Goto {Jump()}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Halt.cs b/Interpreter.Lib/BackEnd/Instructions/Halt.cs deleted file mode 100644 index d9cd0002..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Halt.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public class Halt : Instruction -{ - public Halt(int number) : base(number) - { - } - - public override bool End() => true; - - public override int Execute(VirtualMachine vm) - { - vm.Frames.Pop(); - return -3; - } - - protected override string ToStringRepresentation() => "End"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/IfNotGoto.cs b/Interpreter.Lib/BackEnd/Instructions/IfNotGoto.cs deleted file mode 100644 index 64043003..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/IfNotGoto.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class IfNotGoto : Goto -{ - private readonly IValue _test; - - public IfNotGoto(IValue test, int jump, int number) : - base(jump, number) - { - _test = test; - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - if (!Convert.ToBoolean(_test.Get(frame))) - { - return jump; - } - return Number + 1; - } - - protected override string ToStringRepresentation() => $"IfNot {_test} Goto {Jump()}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/IndexAssignment.cs b/Interpreter.Lib/BackEnd/Instructions/IndexAssignment.cs deleted file mode 100644 index 0d37ed03..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/IndexAssignment.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class IndexAssignment : Simple -{ - public IndexAssignment(string left, (IValue left, IValue right) right, int number) : - base(left, right, "[]", number) - { - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - var obj = (List) frame[Left]; - var index = Convert.ToInt32(right.left.Get(frame)); - obj[index] = right.right.Get(frame); - return Number + 1; - } - - protected override string ToStringRepresentation() => - $"{Left}[{right.left}] = {right.right}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Instruction.cs b/Interpreter.Lib/BackEnd/Instructions/Instruction.cs deleted file mode 100644 index c1688613..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Instruction.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Instructions; - -public abstract class Instruction : IComparable -{ - public int Number { get; } - - protected Instruction(int number) => - Number = number; - - public virtual int Jump() => Number + 1; - - public virtual bool End() => false; - - public abstract int Execute(VirtualMachine vm); - - public int CompareTo(Instruction other) => Number.CompareTo(other.Number); - - protected abstract string ToStringRepresentation(); - - public override string ToString() => $"{Number}: {ToStringRepresentation()}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Print.cs b/Interpreter.Lib/BackEnd/Instructions/Print.cs deleted file mode 100644 index 6fb587bb..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Print.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class Print : Instruction -{ - private readonly IValue _value; - - public Print(int number, IValue value) : base(number) - { - _value = value; - } - - public override int Execute(VirtualMachine vm) - { - vm.Writer.WriteLine(_value.Get(vm.Frames.Peek())); - return Number + 1; - } - - protected override string ToStringRepresentation() => $"Print {_value}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/PushParameter.cs b/Interpreter.Lib/BackEnd/Instructions/PushParameter.cs deleted file mode 100644 index bdacc41e..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/PushParameter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class PushParameter : Instruction -{ - private readonly string _parameter; - private readonly IValue _value; - - public PushParameter( - int number, - string parameter, - IValue value - ) : base(number) - { - _parameter = parameter; - _value = value; - } - - public override int Execute(VirtualMachine vm) - { - vm.Arguments.Push((_parameter, _value.Get(vm.Frames.Peek()))); - return Number + 1; - } - - protected override string ToStringRepresentation() => $"PushParameter {_parameter} = {_value}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/RemoveFromArray.cs b/Interpreter.Lib/BackEnd/Instructions/RemoveFromArray.cs deleted file mode 100644 index e933855e..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/RemoveFromArray.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class RemoveFromArray : Instruction -{ - private readonly string _id; - private readonly IValue _index; - - public RemoveFromArray(int number, string id, IValue index) : base(number) - { - _id = id; - _index = index; - } - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - var list = (List) frame[_id]; - list.RemoveAt(Convert.ToInt32(_index.Get(frame))); - return Number + 1; - } - - protected override string ToStringRepresentation() => - $"RemoveFrom {_id} at {_index}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Return.cs b/Interpreter.Lib/BackEnd/Instructions/Return.cs deleted file mode 100644 index 0a9b3966..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Return.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections; -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class Return : Instruction, IEnumerable -{ - private readonly IValue _value; - private readonly List _callers = new(); - - public int FunctionStart { get; } - - public Return(int functionStart, int number, IValue value = null) : base(number) - { - _value = value; - FunctionStart = functionStart; - } - - public void AddCaller(int caller) => _callers.Add(caller); - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Pop(); - var call = vm.CallStack.Pop(); - if (call.Where != null && _value != null) - { - vm.Frames.Peek()[call.Where] = _value.Get(frame); - } - - return frame.ReturnAddress; - } - - public IEnumerator GetEnumerator() => _callers.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - protected override string ToStringRepresentation() => $"Return{(_value != null ? $" {_value}" : "")}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Instructions/Simple.cs b/Interpreter.Lib/BackEnd/Instructions/Simple.cs deleted file mode 100644 index 8ac1b483..00000000 --- a/Interpreter.Lib/BackEnd/Instructions/Simple.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.BackEnd.Instructions; - -public class Simple : Instruction -{ - public string Left { get; set; } - - protected (IValue left, IValue right) right; - protected readonly string @operator; - - public Simple( - string left, - (IValue left, IValue right) right, - string @operator, - int number - ) : - base(number) - { - Left = left; - this.right = right; - this.@operator = @operator; - } - - public IValue Source => right.right; - - public bool Assignment => @operator == ""; - - public override int Execute(VirtualMachine vm) - { - var frame = vm.Frames.Peek(); - if (right.left == null) - { - var value = right.right.Get(frame); - frame[Left] = @operator switch - { - "-" => -Convert.ToDouble(value), - "!" => !Convert.ToBoolean(value), - "~" => ((List) value).Count, - "" => value, - _ => throw new NotImplementedException() - }; - } - else - { - object lValue = right.left.Get(frame), rValue = right.right.Get(frame); - frame[Left] = @operator switch - { - "+" when lValue is string => lValue.ToString() + rValue, - "+" => Convert.ToDouble(lValue) + Convert.ToDouble(rValue), - "-" => Convert.ToDouble(lValue) - Convert.ToDouble(rValue), - "*" => Convert.ToDouble(lValue) * Convert.ToDouble(rValue), - "/" => Convert.ToDouble(lValue) / Convert.ToDouble(rValue), - "%" => Convert.ToDouble(lValue) % Convert.ToDouble(rValue), - "||" => Convert.ToBoolean(lValue) || Convert.ToBoolean(rValue), - "&&" => Convert.ToBoolean(lValue) && Convert.ToBoolean(rValue), - "==" => Equals(lValue, rValue), - "!=" => !Equals(lValue, rValue), - ">" => Convert.ToDouble(lValue) > Convert.ToDouble(rValue), - ">=" => Convert.ToDouble(lValue) >= Convert.ToDouble(rValue), - "<" => Convert.ToDouble(lValue) < Convert.ToDouble(rValue), - "<=" => Convert.ToDouble(lValue) <= Convert.ToDouble(rValue), - "." => ((Dictionary) lValue)[rValue.ToString()!], - "[]" => ((List) lValue)[Convert.ToInt32(rValue)], - "++" => ((List) lValue).Concat((List) rValue).ToList(), - _ => throw new NotImplementedException() - }; - } - if (vm.CallStack.Any()) - { - var call = vm.CallStack.Peek(); - var methodOf = call.To.MethodOf; - if (methodOf != null) - { - var methodOwner = (Dictionary) frame[methodOf]; - if (methodOwner.ContainsKey(Left)) - { - methodOwner[Left] = frame[Left]; - } - } - } - - return Jump(); - } - - protected override string ToStringRepresentation() => - right.left == null - ? $"{Left} = {@operator}{right.right}" - : $"{Left} = {right.left} {@operator} {right.right}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Values/Constant.cs b/Interpreter.Lib/BackEnd/Values/Constant.cs deleted file mode 100644 index bba39f9b..00000000 --- a/Interpreter.Lib/BackEnd/Values/Constant.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Values; - -public class Constant : IValue -{ - private readonly object _value; - private readonly string _representation; - - public Constant(object value, string representation) - { - _value = value; - _representation = representation; - } - - public object Get(Frame frame) => _value; - - public override string ToString() => _representation; - - public bool Equals(IValue other) - { - if (other is Constant that) - { - return Equals(_value, that._value); - } - - return false; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Values/IValue.cs b/Interpreter.Lib/BackEnd/Values/IValue.cs deleted file mode 100644 index 11bd4a39..00000000 --- a/Interpreter.Lib/BackEnd/Values/IValue.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Values; - -public interface IValue : IEquatable -{ - object Get(Frame frame); -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/Values/Name.cs b/Interpreter.Lib/BackEnd/Values/Name.cs deleted file mode 100644 index 9558c340..00000000 --- a/Interpreter.Lib/BackEnd/Values/Name.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Interpreter.Lib.BackEnd.Values; - -public class Name : IValue -{ - private readonly string _id; - - public Name(string id) - { - _id = id; - } - - public object Get(Frame frame) => frame[_id]; - - public override string ToString() => _id; - - public bool Equals(IValue other) - { - if (other is Name that) - { - return _id == that._id; - } - - return false; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/BackEnd/VirtualMachine.cs b/Interpreter.Lib/BackEnd/VirtualMachine.cs deleted file mode 100644 index a88c5565..00000000 --- a/Interpreter.Lib/BackEnd/VirtualMachine.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.BackEnd; - -public record VirtualMachine( - Stack CallStack, Stack Frames, - Stack<(string Id, object Value)> Arguments, - TextWriter Writer -) -{ - public VirtualMachine() : - this(new(), new(), new(), Console.Out) { } - - public void Run(List instructions) - { - Frames.Push(new Frame()); - - var address = 0; - while (!instructions[address].End()) - { - var instruction = instructions[address]; - var jump = instruction.Execute(this); - address = jump; - } - - instructions[address].Execute(this); - } -} - -public record Call( - int From, FunctionInfo To, - List<(string Id, object Value)> Parameters, - string Where = null) -{ - public override string ToString() => - $"{From} => {To.Location}: {To.Id}({string.Join(", ", Parameters.Select(x => $"{x.Id}: {x.Value}"))})"; -} - -public record FunctionInfo(string Id, int Location = 0, string MethodOf = null) -{ - public int Location { get; set; } = Location; - - public string MethodOf { get; set; } = MethodOf; - - public string CallId() => - MethodOf == null - ? Id - : $"{MethodOf}.{Id}"; - - public override string ToString() => - $"({Location}, {CallId()})"; -} - -public class Frame -{ - private readonly Dictionary _variables = new(); - private readonly Frame _parentFrame; - - public int ReturnAddress { get; } - - public Frame(int returnAddress = 0, Frame parentFrame = null) - { - ReturnAddress = returnAddress; - _parentFrame = parentFrame; - } - - public object this[string id] - { - get => _variables.ContainsKey(id) - ? _variables[id] - : _parentFrame?[id]; - set => _variables[id] = value; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/Structure.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/Structure.cs deleted file mode 100644 index 04a5f964..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/Structure.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Text; -using System.Text.RegularExpressions; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -namespace Interpreter.Lib.FrontEnd.GetTokens.Data; - -public class Structure : IEnumerable -{ - public Structure(List types) - { - types.AddRange(new List - { - TokenTypeUtils.End, - TokenTypeUtils.Error - }); - types = types - .OrderBy(t => t.Priority) - .ToList(); - - Types = types - .ToDictionary(x => x.Tag, x => x); - - Regex = new Regex( - string.Join( - '|', - types - .Where(t => !t.EndOfProgram()) - .Select(t => t.GetNamedRegex()) - .ToList() - ) - ); - } - - private Dictionary Types { get; } - - public Regex Regex { get; } - - public TokenType FindByTag(string tag) => - Types[tag]; - - public override string ToString() => - new StringBuilder() - .AppendJoin('\n', - Types.Select(x => $"{x.Key} {x.Value.Pattern}") - ).ToString(); - - public IEnumerator GetEnumerator() => - Types.Values.GetEnumerator(); - - [ExcludeFromCodeCoverage] - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/EndOfProgramType.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/EndOfProgramType.cs deleted file mode 100644 index bccc907b..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/EndOfProgramType.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -internal record EndOfProgramType() : TokenType("EOP", "", int.MaxValue - 1) -{ - public override bool EndOfProgram() => true; -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/ErrorType.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/ErrorType.cs deleted file mode 100644 index da510a76..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/ErrorType.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -internal record ErrorType() : TokenType("ERROR", @"\S+", int.MaxValue) -{ - public override bool Error() => true; -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/IgnorableType.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/IgnorableType.cs deleted file mode 100644 index 60a3ca59..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/IgnorableType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -public record IgnorableType(string Tag = null, string Pattern = null, int Priority = 0) - : TokenType(Tag, Pattern, Priority) -{ - public override bool CanIgnore() => true; -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenType.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenType.cs deleted file mode 100644 index aacd802f..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -public record TokenType(string Tag, string Pattern, int Priority) -{ - public virtual bool CanIgnore() => false; - - public virtual bool EndOfProgram() => false; - - public virtual bool Error() => false; - - public string GetNamedRegex() => $"(?<{Tag}>{Pattern})"; - - public sealed override string ToString() => Tag; -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenTypeUtils.cs b/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenTypeUtils.cs deleted file mode 100644 index 8d2c6e6a..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/TokenTypes/TokenTypeUtils.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -public static class TokenTypeUtils -{ - public static readonly TokenType End = new EndOfProgramType(); - public static readonly TokenType Error = new ErrorType(); -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/ILexer.cs b/Interpreter.Lib/FrontEnd/GetTokens/ILexer.cs deleted file mode 100644 index 902d7318..00000000 --- a/Interpreter.Lib/FrontEnd/GetTokens/ILexer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.FrontEnd.GetTokens; - -public interface ILexer -{ - Structure Structure { get; } - - List GetTokens(string text); -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/TopDownParse/IParser.cs b/Interpreter.Lib/FrontEnd/TopDownParse/IParser.cs deleted file mode 100644 index 4ab21651..00000000 --- a/Interpreter.Lib/FrontEnd/TopDownParse/IParser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.IR.Ast; - -namespace Interpreter.Lib.FrontEnd.TopDownParse; - -public interface IParser -{ - IAbstractSyntaxTree TopDownParse(string text); -} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/TopDownParse/Impl/Parser.cs b/Interpreter.Lib/FrontEnd/TopDownParse/Impl/Parser.cs deleted file mode 100644 index ef64c670..00000000 --- a/Interpreter.Lib/FrontEnd/TopDownParse/Impl/Parser.cs +++ /dev/null @@ -1,839 +0,0 @@ -using System.Text.RegularExpressions; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.IR.Ast; -using Interpreter.Lib.IR.Ast.Impl; -using Interpreter.Lib.IR.Ast.Nodes; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.ComplexLiterals; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Statements; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; -using Expression = Interpreter.Lib.IR.Ast.Nodes.Expressions.Expression; - -namespace Interpreter.Lib.FrontEnd.TopDownParse.Impl; - -public class Parser : IParser -{ - private TokensStream _tokens; - private readonly ILexer _lexer; - - public Parser(ILexer lexer) => - _lexer = lexer; - - private Token Expect(string expectedTag, string expectedValue = null) - { - var current = _tokens.Current; - - if (!CurrentIs(expectedTag)) - throw new ParserException(_tokens.Current!.Segment, expectedTag, _tokens.Current); - if (_tokens.Current!.Value != (expectedValue ?? _tokens.Current.Value)) - throw new ParserException(_tokens.Current.Segment, expectedValue, _tokens.Current); - - if (CurrentIs(expectedTag) && _tokens.Current.Value == (expectedValue ?? _tokens.Current.Value)) - { - _tokens.MoveNext(); - } - - return current; - } - - private bool CurrentIs(string tag) => - _tokens.Current!.Type == _lexer.Structure.FindByTag(tag); - - private bool CurrentIsLiteral() => - CurrentIs("NullLiteral") || - CurrentIs("IntegerLiteral") || - CurrentIs("FloatLiteral") || - CurrentIs("StringLiteral") || - CurrentIs("BooleanLiteral"); - - private bool CurrentIsKeyword(string keyword) => - CurrentIs("Keyword") && - _tokens.Current!.Value == keyword; - - private bool CurrentIsOperator(string @operator) => - CurrentIs("Operator") && - _tokens.Current!.Value == @operator; - - public IAbstractSyntaxTree TopDownParse(string text) - { - _tokens = _lexer.GetTokens(text); - - var root = Script(SymbolTableUtils.GetStandardLibrary()); - Expect("EOP"); - return new AbstractSyntaxTree(root); - } - - private ScriptBody Script(SymbolTable table = null) => - new(StatementList(table ?? new SymbolTable())) - { - SymbolTable = table ?? new SymbolTable() - }; - - private IEnumerable StatementList(SymbolTable table) - { - var statementList = new List(); - while ( - CurrentIsKeyword("function") || CurrentIsKeyword("let") || CurrentIsKeyword("const") || - CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || - CurrentIs("LeftCurl") || CurrentIsKeyword("return") || CurrentIsKeyword("break") || - CurrentIsKeyword("continue") || CurrentIsKeyword("if") || CurrentIsKeyword("while") || - CurrentIsKeyword("type") - ) - { - statementList.Add(StatementListItem(table)); - } - - return statementList; - } - - private StatementListItem StatementListItem(SymbolTable table) - { - if (CurrentIsKeyword("function") || CurrentIsKeyword("let") || CurrentIsKeyword("const")) - { - return Declaration(table); - } - - return Statement(table); - } - - private Statement Statement(SymbolTable table) - { - if (CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || CurrentIsOperator("-") || - CurrentIsOperator("!")) - { - return ExpressionStatement(table); - } - - if (CurrentIs("LeftCurl")) - { - return BlockStatement(table); - } - - if (CurrentIsKeyword("return")) - { - return ReturnStatement(table); - } - - if (CurrentIsKeyword("break")) - { - return new BreakStatement - { - Segment = Expect("Keyword", "break").Segment - }; - } - - if (CurrentIsKeyword("continue")) - { - return new ContinueStatement - { - Segment = Expect("Keyword", "continue").Segment - }; - } - - if (CurrentIsKeyword("if")) - { - return IfStatement(table); - } - - if (CurrentIsKeyword("while")) - { - return WhileStatement(table); - } - - if (CurrentIsKeyword("type")) - { - return TypeStatement(table); - } - - return null; - } - - private BlockStatement BlockStatement(SymbolTable table) - { - var newTable = new SymbolTable(); - newTable.AddOpenScope(table); - - Expect("LeftCurl"); - var block = new BlockStatement(StatementList(newTable)) - { - SymbolTable = newTable - }; - Expect("RightCurl"); - - return block; - } - - private ExpressionStatement ExpressionStatement(SymbolTable table) - { - return new(Expression(table)); - } - - private ReturnStatement ReturnStatement(SymbolTable table) - { - var ret = Expect("Keyword", "return"); - if (CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || CurrentIsOperator("-") || - CurrentIsOperator("!") || CurrentIs("LeftCurl") || CurrentIs("LeftBracket")) - { - return new ReturnStatement(Expression(table)) - { - Segment = ret.Segment, - SymbolTable = table - }; - } - - return new ReturnStatement - { - Segment = ret.Segment - }; - } - - private IfStatement IfStatement(SymbolTable table) - { - var token = Expect("Keyword", "if"); - Expect("LeftParen"); - var expr = Expression(table); - Expect("RightParen"); - var then = Statement(table); - if (CurrentIsKeyword("else")) - { - Expect("Keyword", "else"); - var @else = Statement(table); - return new IfStatement(expr, then, @else) {SymbolTable = table, Segment = token.Segment}; - } - - return new IfStatement(expr, then) {SymbolTable = table, Segment = token.Segment}; - } - - private WhileStatement WhileStatement(SymbolTable table) - { - var token = Expect("Keyword", "while"); - Expect("LeftParen"); - var expr = Expression(table); - Expect("RightParen"); - var stmt = Statement(table); - return new WhileStatement(expr, stmt) {SymbolTable = table, Segment = token.Segment}; - } - - private TypeStatement TypeStatement(SymbolTable table) - { - var typeWord = Expect("Keyword", "type"); - var ident = Expect("Ident"); - Expect("Assign"); - if (CurrentIs("LeftCurl")) - { - table.AddType(new Type(ident.Value)); - } - var type = TypeValue(table); - - type.Recursive = type.ToString().Contains(ident.Value); - - if (type is ObjectType objectType && type.Recursive) - { - objectType.ResolveSelfReferences(ident.Value); - } - table.AddType(type, ident.Value); - - return new TypeStatement(ident.Value, type) - { - Segment = typeWord.Segment, - SymbolTable = table - }; - } - - private Type TypeValue(SymbolTable table) - { - if (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - var typeFromTable = table.FindType(ident.Value); - if (typeFromTable == null) - { - throw new UnknownIdentifierReference( - new IdentifierReference(ident.Value) - {Segment = ident.Segment} - ); - } - - return WithSuffix(typeFromTable); - } - - if (CurrentIs("LeftCurl")) - { - Expect("LeftCurl"); - var propertyTypes = new List(); - while (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - Expect("Colon"); - var propType = TypeValue(table); - propertyTypes.Add(new PropertyType(ident.Value, propType)); - Expect("SemiColon"); - } - - Expect("RightCurl"); - - return WithSuffix(new ObjectType(propertyTypes)); - } - - if (CurrentIs("LeftParen")) - { - Expect("LeftParen"); - var args = new List(); - while (CurrentIs("Ident") || CurrentIs("LeftCurl") || CurrentIs("LeftParen")) - { - args.Add(TypeValue(table)); - if (!CurrentIs("RightParen")) - { - Expect("Comma"); - } - } - Expect("RightParen"); - Expect("Arrow"); - var returnType = TypeValue(table); - return new FunctionType(returnType, args); - } - - return null; - } - - private Type WithSuffix(Type baseType) - { - var type = baseType; - while (CurrentIs("LeftBracket") || CurrentIs("QuestionMark")) - { - if (CurrentIs("LeftBracket")) - { - Expect("LeftBracket"); - Expect("RightBracket"); - type = new ArrayType(type); - } - else if (CurrentIs("QuestionMark")) - { - Expect("QuestionMark"); - type = new NullableType(type); - } - } - - return type; - } - - private Declaration Declaration(SymbolTable table) - { - if (CurrentIsKeyword("function")) - { - return FunctionDeclaration(table); - } - - if (CurrentIsKeyword("let") || CurrentIsKeyword("const")) - { - return LexicalDeclaration(table); - } - - return null; - } - - private FunctionDeclaration FunctionDeclaration(SymbolTable table) - { - var newTable = new SymbolTable(); - newTable.AddOpenScope(table); - - Expect("Keyword", "function"); - var ident = Expect("Ident"); - - Expect("LeftParen"); - var args = new List(); - if (CurrentIs("Ident")) - { - var arg = Expect("Ident").Value; - Expect("Colon"); - var type = TypeValue(table); - args.Add(new VariableSymbol(arg, type)); - } - - while (CurrentIs("Comma")) - { - Expect("Comma"); - var arg = Expect("Ident").Value; - Expect("Colon"); - var type = TypeValue(table); - args.Add(new VariableSymbol(arg, type)); - } - - Expect("RightParen"); - - var returnType = TypeUtils.JavaScriptTypes.Void; - if (CurrentIs("Colon")) - { - Expect("Colon"); - returnType = TypeValue(table); - } - - var functionSymbol = - new FunctionSymbol(ident.Value, args, - new FunctionType(returnType, args.Select(x => x.Type)) - ); - table.AddSymbol(functionSymbol); - - return new FunctionDeclaration(functionSymbol, BlockStatement(newTable)) - { - Segment = ident.Segment, - SymbolTable = newTable - }; - } - - private LexicalDeclaration LexicalDeclaration(SymbolTable table) - { - var readOnly = CurrentIsKeyword("const"); - Expect("Keyword", readOnly ? "const" : "let"); - var declaration = new LexicalDeclaration(readOnly) - { - SymbolTable = table - }; - - AddToDeclaration(declaration, table); - - while (CurrentIs("Comma")) - { - Expect("Comma"); - AddToDeclaration(declaration, table); - } - - return declaration; - } - - private void AddToDeclaration(LexicalDeclaration declaration, SymbolTable table) - { - var ident = Expect("Ident"); - if (CurrentIs("Assign")) - { - var assignSegment = Expect("Assign").Segment; - declaration.AddAssignment(ident.Value, ident.Segment, Expression(table), assignSegment); - } - else if (CurrentIs("Colon")) - { - Expect("Colon"); - var type = TypeValue(table); - if (CurrentIs("Assign")) - { - var assignSegment = Expect("Assign").Segment; - declaration.AddAssignment(ident.Value, ident.Segment, Expression(table), assignSegment, type); - } - else - { - declaration.AddAssignment( - ident.Value, - ident.Segment, - new Literal( - type, - TypeUtils.GetDefaultValue(type), - label: TypeUtils.GetDefaultValue(type) == null ? "null" : null - ) - ); - } - } - } - - private Expression Expression(SymbolTable table) - { - return AssignmentExpression(table); - } - - private Expression AssignmentExpression(SymbolTable table) - { - var lhs = LeftHandSideExpression(table); - if (CurrentIs("Assign") && !(lhs is CallExpression)) - { - var assign = Expect("Assign"); - var member = lhs is IdentifierReference reference - ? (MemberExpression) reference - : (MemberExpression) lhs; - return new AssignmentExpression(member, AssignmentExpression(table)) - {SymbolTable = table, Segment = assign.Segment}; - } - - return lhs; - } - - private Expression LeftHandSideExpression(SymbolTable table) - { - var expr = CastExpression(table); - if (expr is IdentifierReference identRef) - { - if (CurrentIs("LeftParen") || CurrentIs("LeftBracket") || CurrentIs("Dot")) - { - return CallExpression(identRef, table); - } - } - - return expr; - } - - private Expression CallExpression(IdentifierReference identRef, SymbolTable table) - { - var member = MemberExpression(identRef, table); - if (CurrentIs("LeftParen")) - { - var lp = Expect("LeftParen"); - var expressions = new List(); - if (CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || CurrentIsOperator("-")) - { - expressions.Add(Expression(table)); - } - - while (CurrentIs("Comma")) - { - Expect("Comma"); - expressions.Add(Expression(table)); - } - - Expect("RightParen"); - return new CallExpression(member, expressions) - { - SymbolTable = table, - Segment = lp.Segment - }; - } - - return member; - } - - private MemberExpression MemberExpression(IdentifierReference identRef, SymbolTable table) - { - var accessChain = new List(); - while (CurrentIs("LeftBracket") || CurrentIs("Dot")) - { - Token access; - if (CurrentIs("LeftBracket")) - { - access = Expect("LeftBracket"); - var lb = access.Segment; - var expr = Expression(table); - var rb = Expect("RightBracket").Segment; - accessChain.Add( - new IndexAccess(expr, accessChain.LastOrDefault()) {Segment = lb + rb} - ); - } - else if (CurrentIs("Dot")) - { - access = Expect("Dot"); - var identToken = Expect("Ident"); - var idRef = new IdentifierReference(identToken.Value) - { - Segment = identToken.Segment, - SymbolTable = table - }; - accessChain.Add( - new DotAccess(idRef, accessChain.LastOrDefault()) {Segment = access.Segment} - ); - } - } - - return new MemberExpression(identRef, accessChain.FirstOrDefault()) - { - SymbolTable = table - }; - } - - private Expression CastExpression(SymbolTable table) - { - var cond = ConditionalExpression(table); - if (CurrentIsKeyword("as")) - { - var asKeyword = Expect("Keyword", "as"); - var type = TypeValue(table); - return new CastAsExpression(cond, type) {Segment = asKeyword.Segment}; - } - - return cond; - } - - private Expression ConditionalExpression(SymbolTable table) - { - var test = OrExpression(table); - if (CurrentIs("QuestionMark")) - { - Expect("QuestionMark"); - var consequent = AssignmentExpression(table); - Expect("Colon"); - var alternate = AssignmentExpression(table); - return new ConditionalExpression(test, consequent, alternate); - } - - return test; - } - - private Expression OrExpression(SymbolTable table) - { - var left = AndExpression(table); - while (CurrentIsOperator("||")) - { - var op = Expect("Operator"); - var right = AndExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression AndExpression(SymbolTable table) - { - var left = EqualityExpression(table); - while (CurrentIsOperator("&&")) - { - var op = Expect("Operator"); - var right = EqualityExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression EqualityExpression(SymbolTable table) - { - var left = RelationExpression(table); - while (CurrentIsOperator("==") || CurrentIsOperator("!=")) - { - var op = Expect("Operator"); - var right = RelationExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression RelationExpression(SymbolTable table) - { - var left = AdditiveExpression(table); - while (CurrentIsOperator(">") || CurrentIsOperator("<") || CurrentIsOperator(">=") || - CurrentIsOperator("<=")) - { - var op = Expect("Operator"); - var right = AdditiveExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression AdditiveExpression(SymbolTable table) - { - var left = MultiplicativeExpression(table); - while (CurrentIsOperator("+") || CurrentIsOperator("-")) - { - var op = Expect("Operator"); - var right = MultiplicativeExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression MultiplicativeExpression(SymbolTable table) - { - var left = UnaryExpression(table); - while (CurrentIsOperator("*") || CurrentIsOperator("/") || CurrentIsOperator("%") - || CurrentIsOperator("++") || CurrentIsOperator("::")) - { - var op = Expect("Operator"); - var right = UnaryExpression(table); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - private Expression UnaryExpression(SymbolTable table) - { - if (CurrentIsOperator("-") || CurrentIsOperator("!") || CurrentIsOperator("~")) - { - var op = Expect("Operator"); - return new UnaryExpression(op.Value, UnaryExpression(table)) - { - Segment = op.Segment - }; - } - - return PrimaryExpression(table); - } - - private Expression PrimaryExpression(SymbolTable table) - { - if (CurrentIs("LeftParen")) - { - Expect("LeftParen"); - var expr = Expression(table); - Expect("RightParen"); - return expr; - } - - if (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - var id = new IdentifierReference(ident.Value) - { - Segment = ident.Segment, - SymbolTable = table - }; - - return id; - } - - if (CurrentIsLiteral()) - { - return Literal(); - } - - if (CurrentIs("LeftCurl")) - { - return ObjectLiteral(table); - } - - if (CurrentIs("LeftBracket")) - { - return ArrayLiteral(table); - } - - return null; - } - - private Literal Literal() - { - var segment = _tokens.Current!.Segment; - if (CurrentIs("StringLiteral")) - { - var str = Expect("StringLiteral"); - return new Literal( - TypeUtils.JavaScriptTypes.String, - Regex.Unescape(str.Value.Trim('"')), - segment, - str.Value - .Replace(@"\", @"\\") - .Replace(@"""", @"\""") - ); - } - - return _tokens.Current.Type.Tag switch - { - "NullLiteral" => new Literal(TypeUtils.JavaScriptTypes.Null, - Expect("NullLiteral").Value == "null" ? null : "", segment, "null"), - "IntegerLiteral" => new Literal(TypeUtils.JavaScriptTypes.Number, - double.Parse(Expect("IntegerLiteral").Value), segment), - "FloatLiteral" => new Literal(TypeUtils.JavaScriptTypes.Number, - double.Parse(Expect("FloatLiteral").Value), segment), - "BooleanLiteral" => new Literal(TypeUtils.JavaScriptTypes.Boolean, - bool.Parse(Expect("BooleanLiteral").Value), segment), - _ => new Literal(TypeUtils.JavaScriptTypes.Undefined, new TypeUtils.Undefined()) - }; - } - - private ObjectLiteral ObjectLiteral(SymbolTable table) - { - var newTable = new SymbolTable(); - newTable.AddOpenScope(table); - Expect("LeftCurl"); - var properties = new List(); - var methods = new List(); - while (CurrentIs("Ident")) - { - var idToken = Expect("Ident"); - var id = new IdentifierReference(idToken.Value) - { - Segment = idToken.Segment, - SymbolTable = newTable - }; - if (CurrentIs("Colon")) - { - Expect("Colon"); - var expr = Expression(newTable); - properties.Add(new Property(id, expr)); - } - else if (CurrentIs("Arrow")) - { - Expect("Arrow"); - Expect("LeftParen"); - var args = new List(); - while (CurrentIs("Ident")) - { - var name = Expect("Ident").Value; - Expect("Colon"); - var type = TypeValue(newTable); - args.Add(new VariableSymbol(name, type)); - if (!CurrentIs("RightParen")) - { - Expect("Comma"); - } - } - Expect("RightParen"); - var returnType = TypeUtils.JavaScriptTypes.Void; - if (CurrentIs("Colon")) - { - Expect("Colon"); - returnType = TypeValue(newTable); - } - - var functionSymbol = new FunctionSymbol(idToken.Value, args, - new FunctionType(returnType, args.Select(a => a.Type)) - ); - newTable.AddSymbol(functionSymbol); - var bodyTable = new SymbolTable(); - bodyTable.AddOpenScope(newTable); - methods.Add(new FunctionDeclaration(functionSymbol, BlockStatement(bodyTable)) - { - Segment = idToken.Segment, - SymbolTable = bodyTable - }); - } - - Expect("SemiColon"); - } - Expect("RightCurl"); - return new ObjectLiteral(properties, methods) - { - SymbolTable = newTable - }; - } - - private ArrayLiteral ArrayLiteral(SymbolTable table) - { - var lb = Expect("LeftBracket").Segment; - var expressions = new List(); - while (CurrentIs("Ident") || CurrentIsLiteral() || - CurrentIs("LeftParen") || CurrentIsOperator("-") || - CurrentIsOperator("!") || CurrentIs("LeftCurl") || - CurrentIs("LeftBracket")) - { - expressions.Add(Expression(table)); - if (!CurrentIs("RightBracket")) - { - Expect("Comma"); - } - } - var rb = Expect("RightBracket").Segment; - return new ArrayLiteral(expressions) {Segment = lb + rb}; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/GlobalUsings.cs b/Interpreter.Lib/GlobalUsings.cs deleted file mode 100644 index ab06ac66..00000000 --- a/Interpreter.Lib/GlobalUsings.cs +++ /dev/null @@ -1,3 +0,0 @@ -// Global using directives - -global using Type = Interpreter.Lib.IR.CheckSemantics.Types.Type; \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/IAbstractSyntaxTree.cs b/Interpreter.Lib/IR/Ast/IAbstractSyntaxTree.cs deleted file mode 100644 index 15e5f494..00000000 --- a/Interpreter.Lib/IR/Ast/IAbstractSyntaxTree.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.IR.Ast; - -public interface IAbstractSyntaxTree -{ - List GetInstructions(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Impl/AbstractSyntaxTree.cs b/Interpreter.Lib/IR/Ast/Impl/AbstractSyntaxTree.cs deleted file mode 100644 index a04f5e1b..00000000 --- a/Interpreter.Lib/IR/Ast/Impl/AbstractSyntaxTree.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast.Nodes; - -namespace Interpreter.Lib.IR.Ast.Impl; - -public class AbstractSyntaxTree : IAbstractSyntaxTree -{ - private readonly AbstractSyntaxTreeNode _root; - - public AbstractSyntaxTree(AbstractSyntaxTreeNode root) - { - _root = root; - } - - private void Check() => - GetAllNodes().ToList().ForEach(node => node.SemanticCheck()); - - private IEnumerable GetAllNodes() => - _root.GetAllNodes(); - - public List GetInstructions() - { - Check(); - - var start = 0; - var result = new List(); - foreach (var node in _root) - { - var instructions = node.ToInstructions(start); - result.AddRange(instructions); - start += instructions.Count; - } - - result.Sort(); - result.Add(new Halt(result.Count)); - - var calls = result.OfType().GroupBy(i => i.Jump()); - foreach (var call in calls) - { - var returns = result.OfType() - .Where(r => r.FunctionStart == call.Key); - foreach (var ret in returns) - { - foreach (var caller in call) - { - ret.AddCaller(caller.Number + 1); - } - } - } - return result; - } - - public override string ToString() - { - var tree = new StringBuilder("digraph ast {\n"); - _root.GetAllNodes().ForEach(node => - { - tree.Append('\t').Append(node).Append('\n'); - node.ToList().ForEach(child => tree.Append($"\t{node.GetHashCode()}->{child.GetHashCode()}\n")); - }); - return tree.Append("}\n").ToString(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/AbstractSyntaxTreeNode.cs b/Interpreter.Lib/IR/Ast/Nodes/AbstractSyntaxTreeNode.cs deleted file mode 100644 index 0c241ad1..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/AbstractSyntaxTreeNode.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.CheckSemantics.Variables; - -namespace Interpreter.Lib.IR.Ast.Nodes; - -public abstract class AbstractSyntaxTreeNode : IEnumerable -{ - public AbstractSyntaxTreeNode Parent { get; set; } - - public SymbolTable SymbolTable { get; set; } - - public bool CanEvaluate { get; protected init; } - - public Segment Segment { get; init; } - - protected AbstractSyntaxTreeNode() - { - Parent = null; - CanEvaluate = false; - } - - internal List GetAllNodes() - { - var result = new List - { - this - }; - foreach (var child in this) - { - result.AddRange(child.GetAllNodes()); - } - - return result; - } - - public bool ChildOf() where T : AbstractSyntaxTreeNode - { - var parent = Parent; - while (parent != null) - { - if (parent is T) - { - return true; - } - parent = parent.Parent; - } - - return false; - } - - public void SemanticCheck() - { - if (CanEvaluate && !ChildOf()) - { - NodeCheck(); - } - } - - internal virtual Type NodeCheck() => null; - - public virtual List ToInstructions(int start) => new (); - - public abstract IEnumerator GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - protected abstract string NodeRepresentation(); - - public override string ToString() => $"{GetHashCode()} [label=\"{NodeRepresentation()}\"]"; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Declarations/Declaration.cs b/Interpreter.Lib/IR/Ast/Nodes/Declarations/Declaration.cs deleted file mode 100644 index 426a64fb..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Declarations/Declaration.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes.Declarations; - -public abstract class Declaration : StatementListItem -{ - public override bool IsDeclaration() => true; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Declarations/FunctionDeclaration.cs b/Interpreter.Lib/IR/Ast/Nodes/Declarations/FunctionDeclaration.cs deleted file mode 100644 index 7d578fde..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Declarations/FunctionDeclaration.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Statements; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Declarations; - -public class FunctionDeclaration : Declaration -{ - private readonly FunctionSymbol _function; - - private readonly BlockStatement _statements; - - public FunctionDeclaration(FunctionSymbol function, BlockStatement statements) - { - _function = function; - function.Body = this; - - _statements = statements; - _statements.Parent = this; - } - - public bool HasReturnStatement() => _statements.HasReturnStatement(); - - public void SetArguments(CallExpression call, List expressions) - { - if (_function.Type.Arguments.Count == expressions.Count) - { - expressions.Select((e, i) => (e, i)).ToList() - .ForEach(pair => - { - var (e, i) = pair; - var eType = e.NodeCheck(); - if (_function.Type.Arguments[i].Equals(eType)) - { - SymbolTable.AddSymbol(_function.Parameters[i]); - } - else throw new WrongTypeOfArgument(e.Segment, _function.Type.Arguments[i], eType); - }); - } - else throw new WrongNumberOfArguments(call.Segment, _function.Parameters.Count, expressions.Count); - } - - public void Clear() - { - _statements.GetAllNodes().ForEach(x => x.SymbolTable?.Clear()); - SymbolTable.Clear(); - } - - public FunctionSymbol GetSymbol() => _function; - - public override IEnumerator GetEnumerator() - { - yield return _statements; - } - - protected override string NodeRepresentation() => $"function {_function.Id}"; - - public override List ToInstructions(int start) - { - var instructions = new List(); - if (_statements.Any()) - { - _function.CallInfo.Location = start + 1; - - var body = new List - { - new BeginFunction(_function.CallInfo.Location, _function.CallInfo) - }; - body.AddRange(_statements.ToInstructions(_function.CallInfo.Location + 1)); - if (!_statements.HasReturnStatement()) - { - body.Add(new Return(_function.CallInfo.Location, body.Last().Number + 1)); - } - - instructions.Add(new Goto(body.Last().Number + 1, start)); - - instructions.AddRange(body); - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Declarations/LexicalDeclaration.cs b/Interpreter.Lib/IR/Ast/Nodes/Declarations/LexicalDeclaration.cs deleted file mode 100644 index 056519e4..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Declarations/LexicalDeclaration.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Declarations; - -public class LexicalDeclaration : Declaration -{ - private readonly DeclarationType _declarationType; - private readonly List _assignments = new(); - - public LexicalDeclaration(bool readOnly) - { - _declarationType = readOnly ? DeclarationType.Const : DeclarationType.Let; - } - - public void AddAssignment(string id, Segment identSegment, Expression expression, Segment assignSegment = null, Type destinationType = null) - { - var identRef = new IdentifierReference(id) - { - SymbolTable = SymbolTable, - Segment = identSegment - }; - var assignment = - new AssignmentExpression( - new MemberExpression(identRef, null), - expression, - destinationType - ) - { - SymbolTable = SymbolTable, - Segment = assignSegment, - Parent = this - }; - _assignments.Add(assignment); - } - - public bool Const() => _declarationType == DeclarationType.Const; - - public override IEnumerator GetEnumerator() => _assignments.GetEnumerator(); - - protected override string NodeRepresentation() => _declarationType.ToString(); - - public override List ToInstructions(int start) - { - var instructions = new List(); - var offset = start; - foreach (var aInstructions in _assignments.Select(assignment => assignment.ToInstructions(offset))) - { - instructions.AddRange(aInstructions); - offset += aInstructions.Count; - } - - return instructions; - } - - private enum DeclarationType - { - Let, - Const - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs deleted file mode 100644 index 9ffd5e11..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; - -public abstract class AccessExpression : Expression -{ - public AccessExpression Next { get; private set; } - - protected AccessExpression(AccessExpression prev) - { - if (prev != null) - { - Parent = prev; - prev.Next = this; - } - } - - public AccessExpression Tail - { - get - { - var head = this; - while (head.HasNext()) - { - head = head.Next; - } - - return head; - } - } - - public abstract Type Check(Type prev); - - public bool HasNext() => Next != null; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs deleted file mode 100644 index b2ea92b8..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; - -public class DotAccess : AccessExpression -{ - private readonly IdentifierReference _id; - - public DotAccess(IdentifierReference id, AccessExpression prev = null) : base(prev) - { - _id = id; - _id.Parent = this; - } - - public string Id => _id.Id; - - public override Type Check(Type prev) - { - if (prev is ObjectType objectType) - { - var fieldType = objectType[_id.Id]; - if (fieldType != null) - { - return HasNext() ? Next.Check(fieldType) : fieldType; - } - - throw new ObjectAccessException(Segment, objectType, _id.Id); - } - - return null; - } - - public override IEnumerator GetEnumerator() - { - yield return _id; - if (HasNext()) - { - yield return Next; - } - } - - protected override string NodeRepresentation() => "."; - - public override List ToInstructions(int start, string temp) - { - if (HasNext()) - { - var left = "_t" + start; - var nextInstructions = Next.ToInstructions(start + 1, left); - nextInstructions.Insert(0, - new Simple(left, (new Name(temp), new Constant(_id.Id, _id.Id)), ".", start) - ); - return nextInstructions; - } - - return new(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs deleted file mode 100644 index f733a7a1..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; - -public class IndexAccess : AccessExpression -{ - private readonly Expression _expression; - - public IndexAccess(Expression expression, AccessExpression prev = null) : base(prev) - { - _expression = expression; - _expression.Parent = this; - } - - public PrimaryExpression Expression => _expression as PrimaryExpression; - - public override IEnumerator GetEnumerator() - { - yield return _expression; - if (HasNext()) - { - yield return Next; - } - } - - public override Type Check(Type prev) - { - if (prev is ArrayType arrayType) - { - var indexType = _expression.NodeCheck(); - if (indexType.Equals(TypeUtils.JavaScriptTypes.Number)) - { - var elemType = arrayType.Type; - return HasNext() ? Next.Check(elemType) : elemType; - } - - throw new ArrayAccessException(Segment, indexType); - } - - return null; - } - - protected override string NodeRepresentation() => "[]"; - - public override List ToInstructions(int start, string temp) - { - if (HasNext()) - { - if (_expression is PrimaryExpression prim) - { - var left = "_t" + start; - var nextInstructions = Next.ToInstructions(start + 1, left); - nextInstructions.Insert(0, - new Simple(left, (new Name(temp), prim.ToValue()), "[]", start) - ); - return nextInstructions; - } - - throw new NotImplementedException(); - } - - return new(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AssignmentExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/AssignmentExpression.cs deleted file mode 100644 index b98f1299..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/AssignmentExpression.cs +++ /dev/null @@ -1,183 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class AssignmentExpression : Expression -{ - private readonly MemberExpression _destination; - private readonly Expression _source; - private readonly Type _destinationType; - - public AssignmentExpression(MemberExpression destination, Expression source, Type destinationType = null) - { - _destination = destination; - destination.Parent = this; - - _source = source; - source.Parent = this; - - _destinationType = destinationType; - } - - internal override Type NodeCheck() - { - var id = _destination.Id; - var type = _source.NodeCheck(); - if (Parent is LexicalDeclaration declaration) - { - if (declaration.Const() && type.Equals(TypeUtils.JavaScriptTypes.Undefined)) - { - throw new ConstWithoutInitializer(_destination); - } - - if (SymbolTable.ContainsSymbol(_destination.Id)) - { - throw new DeclarationAlreadyExists(_destination); - } - - if (_destinationType != null && type.Equals(TypeUtils.JavaScriptTypes.Undefined)) - { - type = _destinationType; - } - - if (_destinationType != null && !_destinationType.Equals(type)) - { - throw new IncompatibleTypesOfOperands(Segment, _destinationType, type); - } - - if (_destinationType == null && type.Equals(TypeUtils.JavaScriptTypes.Undefined)) - { - throw new CannotDefineType(Segment); - } - - var typeOfSymbol = _destinationType != null && type.Equals(TypeUtils.JavaScriptTypes.Undefined) - ? _destinationType - : type; - if (typeOfSymbol is ObjectType objectTypeOfSymbol) - { - SymbolTable.AddSymbol(new ObjectSymbol(id, objectTypeOfSymbol, declaration.Const(), _source.SymbolTable) - { - Table = _source.SymbolTable - }); - } - else - { - SymbolTable.AddSymbol(new VariableSymbol(id, typeOfSymbol, declaration.Const())); - } - } - else - { - var symbol = SymbolTable.FindSymbol(id); - if (symbol != null) - { - if (symbol.ReadOnly) - { - throw new AssignmentToConst(_destination); - } - - if (!_destination.NodeCheck().Equals(type)) - { - throw new IncompatibleTypesOfOperands(Segment, symbol.Type, type); - } - } - } - - return type; - } - - public override List ToInstructions(int start) - { - var instructions = new List(); - var destInstructions = _destination.ToInstructions(start, _destination.Id); - var srcInstructions = _source.ToInstructions(start + destInstructions.Count, _destination.Id); - - instructions.AddRange(destInstructions); - instructions.AddRange(srcInstructions); - start += instructions.Count; - - if (_source is MemberExpression member && member.Any()) - { - var access = (member.First() as AccessExpression)?.Tail; - var dest = destInstructions.Any() - ? destInstructions.OfType().Last().Left - : _destination.Id; - var src = srcInstructions.Any() - ? srcInstructions.OfType().Last().Left - : member.Id; - var instruction = access switch - { - DotAccess dot => new Simple(dest, (new Name(src), new Constant(dot.Id, dot.Id)), ".", start), - IndexAccess index => new Simple(dest, (new Name(src), index.Expression.ToValue()), "[]", start), - _ => throw new NotImplementedException() - }; - instructions.Add(instruction); - start++; - } - - var last = instructions.OfType().LastOrDefault(); - if (last != null) - { - if (_source is AssignmentExpression) - { - instructions.Add(new Simple( - _destination.Id, - (null, new Name(last.Left)), - "", last.Jump() - )); - start++; - } - else - { - last.Left = _destination.Id; - } - } - - if (_destination.Any()) - { - var access = (_destination.First() as AccessExpression)?.Tail; - var lastIndex = instructions.Count - 1; - last = instructions.OfType().Last(); - if (last.Assignment) - { - instructions.RemoveAt(lastIndex); - start--; - } - else - { - last.Left = "_t" + last.Number; - } - - var dest = destInstructions.Any() - ? destInstructions.OfType().Last().Left - : _destination.Id; - var src = !last.Assignment - ? new Name(last.Left) - : last.Source; - Instruction instruction = access switch - { - DotAccess dot => new DotAssignment(dest, (new Constant(dot.Id, @$"\""{dot.Id}\"""), src), start), - IndexAccess index => new IndexAssignment(dest, (index.Expression.ToValue(), src), start), - _ => throw new NotImplementedException() - }; - instructions.Add(instruction); - } - - return instructions; - } - - public override IEnumerator GetEnumerator() - { - yield return _destination; - yield return _source; - } - - protected override string NodeRepresentation() => "="; - - public override List ToInstructions(int start, string temp) => ToInstructions(start); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/BinaryExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/BinaryExpression.cs deleted file mode 100644 index 3d3963ea..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/BinaryExpression.cs +++ /dev/null @@ -1,209 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class BinaryExpression : Expression -{ - private readonly Expression _left; - - private readonly string _operator; - - private readonly Expression _right; - - public BinaryExpression(Expression left, string @operator, Expression right) - { - _left = left; - _left.Parent = this; - - _operator = @operator; - - _right = right; - _right.Parent = this; - } - - internal override Type NodeCheck() - { - var lType = _left.NodeCheck(); - var rType = _right.NodeCheck(); - Type retType = null; - if (_operator != "::" && !lType.Equals(rType)) - { - throw new IncompatibleTypesOfOperands(Segment, lType, rType); - } - - switch (_operator) - { - case "+": - if (lType.Equals(TypeUtils.JavaScriptTypes.Number)) - { - retType = TypeUtils.JavaScriptTypes.Number; - } - else if (lType.Equals(TypeUtils.JavaScriptTypes.String)) - { - retType = TypeUtils.JavaScriptTypes.String; - } - else throw new UnsupportedOperation(Segment, lType, _operator); - - break; - case "-": - case "*": - case "/": - case "%": - if (lType.Equals(TypeUtils.JavaScriptTypes.Number)) - { - retType = TypeUtils.JavaScriptTypes.Number; - } - else throw new UnsupportedOperation(Segment, lType, _operator); - - break; - case "||": - case "&&": - if (lType.Equals(TypeUtils.JavaScriptTypes.Boolean)) - { - retType = TypeUtils.JavaScriptTypes.Boolean; - } - else throw new UnsupportedOperation(Segment, lType, _operator); - - break; - case "==": - case "!=": - retType = TypeUtils.JavaScriptTypes.Boolean; - break; - case ">": - case ">=": - case "<": - case "<=": - if (lType.Equals(TypeUtils.JavaScriptTypes.Number)) - { - retType = TypeUtils.JavaScriptTypes.Boolean; - } - else throw new UnsupportedOperation(Segment, lType, _operator); - - break; - case "++": - if (lType is ArrayType && rType is ArrayType) - { - retType = lType; - } - else throw new UnsupportedOperation(Segment, lType, _operator); - - break; - case "::": - if (!(lType is ArrayType)) - { - throw new UnsupportedOperation(Segment, lType, _operator); - } - if (rType.Equals(TypeUtils.JavaScriptTypes.Number)) - { - retType = TypeUtils.JavaScriptTypes.Void; - } - else throw new ArrayAccessException(Segment, rType); - - break; - } - - return retType; - } - - public override IEnumerator GetEnumerator() - { - yield return _left; - yield return _right; - } - - protected override string NodeRepresentation() => _operator; - - public override List ToInstructions(int start) - { - if (_left is IdentifierReference arr && _right.Primary() && _operator == "::") - { - return new List - { - new RemoveFromArray(start, arr.Id, ((PrimaryExpression) _right).ToValue()) - }; - } - - throw new NotImplementedException(); - } - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - (IValue left, IValue right) newRight = (null, null); - - var lInstructions = new List(); - var rInstructions = new List(); - - if (_left.Primary()) - { - newRight.left = ((PrimaryExpression) _left).ToValue(); - } - else - { - lInstructions.AddRange(_left.ToInstructions(start, temp)); - if (_left is MemberExpression member && member.Any()) - { - var i = start + lInstructions.Count; - var dest = "_t" + i; - var src = lInstructions.Any() - ? lInstructions.OfType().Last().Left - : member.Id; - var instruction = member.AccessChain.Tail switch - { - DotAccess dot => new Simple(dest, (new Name(src), new Constant(dot.Id, dot.Id)), ".", i), - IndexAccess index => new Simple(dest, (new Name(src), index.Expression.ToValue()), "[]", i), - _ => throw new NotImplementedException() - }; - lInstructions.Add(instruction); - } - newRight.left = new Name(lInstructions.OfType().Last().Left); - } - - if (_right.Primary()) - { - newRight.right = ((PrimaryExpression) _right).ToValue(); - } - else - { - var c = _left.Primary() - ? start - : lInstructions.Last().Number + 1; - rInstructions.AddRange(_right.ToInstructions(c, temp)); - if (_right is MemberExpression member && member.Any()) - { - var i = c + rInstructions.Count; - var dest = "_t" + i; - var src = rInstructions.Any() - ? rInstructions.OfType().Last().Left - : member.Id; - var instruction = member.AccessChain.Tail switch - { - DotAccess dot => new Simple(dest, (new Name(src), new Constant(dot.Id, dot.Id)), ".", i), - IndexAccess index => new Simple(dest, (new Name(src), index.Expression.ToValue()), "[]", i), - _ => throw new NotImplementedException() - }; - rInstructions.Add(instruction); - } - newRight.right = new Name(rInstructions.OfType().Last().Left); - } - - instructions.AddRange(lInstructions); - instructions.AddRange(rInstructions); - - var number = instructions.Any() ? instructions.Last().Number + 1 : start; - - instructions.Add - ( - new Simple( - temp + number, - newRight, _operator, number - ) - ); - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/CallExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/CallExpression.cs deleted file mode 100644 index 60d46166..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/CallExpression.cs +++ /dev/null @@ -1,195 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Statements; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class CallExpression : Expression -{ - private readonly MemberExpression _ident; - private readonly List _expressions; - - public CallExpression(MemberExpression ident, IEnumerable expressions) - { - _ident = ident; - _ident.Parent = this; - - _expressions = new List(expressions); - _expressions.ForEach(expr => expr.Parent = this); - } - - private FunctionSymbol GetFunction() - { - if (_ident.Any()) - { - var table = SymbolTable.FindSymbol(_ident.Id).Table; - var chain = _ident.AccessChain; - while (chain.HasNext()) - { - table = chain switch - { - DotAccess dot => table.FindSymbol(dot.Id).Table, - IndexAccess => throw new NotImplementedException(), - _ => throw new NotImplementedException() - }; - chain = chain.Next; - } - - return table.FindSymbol(((DotAccess) chain).Id); - } - - return SymbolTable.FindSymbol(_ident.Id); - } - - internal override Type NodeCheck() - { - if (_ident.Any()) - { - _ident.NodeCheck(); - } - else - { - IdentifierReference idRef = _ident; - idRef.NodeCheck(); - } - - var function = GetFunction(); - if (function == null) - { - throw new SymbolIsNotCallable(_ident.Id, Segment); - } - - if (!function.Type.ReturnType.Equals(TypeUtils.JavaScriptTypes.Void)) - { - if (!function.Body.HasReturnStatement()) - { - throw new FunctionWithoutReturnStatement(function.Body.Segment); - } - } - - function.Body.SetArguments(this, _expressions); - - var block = function.Body.First().GetAllNodes(); - foreach (var node in block) - { - if (node is ReturnStatement retStmt) - { - var retType = retStmt.NodeCheck(); - if (retType.Equals(function.Type.ReturnType)) - { - function.Body.Clear(); - return retType; - } - - throw new WrongReturnType(retStmt.Segment, function.Type.ReturnType, retType); - } - - if (node.CanEvaluate && !(node is CallExpression call && call._ident.Id == _ident.Id)) - { - node.NodeCheck(); - } - } - - function.Body.Clear(); - return TypeUtils.JavaScriptTypes.Void; - } - - public override IEnumerator GetEnumerator() - { - var nodes = new List - { - _ident - }; - nodes.AddRange(_expressions); - return nodes.GetEnumerator(); - } - - protected override string NodeRepresentation() => "()"; - - private List Print(int start) - { - var instructions = new List(); - var expression = _expressions.First(); - if (!expression.Primary()) - { - instructions.AddRange(expression.ToInstructions(start, "_t")); - instructions.Add(new Print( - instructions.Last().Number + 1, - new Name(instructions.OfType().Last().Left) - )); - } - else - { - instructions.Add(new Print(start, ((PrimaryExpression) expression).ToValue())); - } - - return instructions; - } - - public override List ToInstructions(int start) - { - return _ident.Id switch - { - "print" when !_ident.Any() => Print(start), - _ => ToInstructions(start, null) - }; - } - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - FunctionSymbol function; - if (!_ident.Any()) - { - function = SymbolTable.FindSymbol(_ident.Id); - } - else - { - function = GetFunction(); - instructions.AddRange(_ident.ToInstructions(start, temp)); - function.CallInfo.MethodOf = instructions.Any() - ? instructions.OfType().Last().Left - : function.CallInfo.MethodOf; - instructions.Add( - new PushParameter( - start + instructions.Count, - "this", new Name(function.CallInfo.MethodOf)) - ); - } - if (function.Body.First().Any()) - { - _expressions.Zip(function.Parameters).ToList<(Expression expr, Symbol param)>() - .ForEach(item => - { - var (expr, symbol) = item; - var paramInstructions = expr.Primary() - ? new List() - : expr.ToInstructions(start, "_t"); - var pushNumber = start + instructions.Count + paramInstructions.Count; - var pushValue = expr.Primary() - ? ((PrimaryExpression) expr).ToValue() - : new Name(paramInstructions.OfType().Last().Left); - paramInstructions.Add( - new PushParameter(pushNumber, symbol.Id, pushValue) - ); - instructions.AddRange(paramInstructions); - }); - var left = temp != null - ? temp + (start + instructions.Count) - : null; - instructions.Add( - new CallFunction( - function.CallInfo, - start + instructions.Count, - function.Parameters.Count, left - )); - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/CastAsExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/CastAsExpression.cs deleted file mode 100644 index 5c9f5503..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/CastAsExpression.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class CastAsExpression : Expression -{ - private readonly Expression _expression; - private readonly Type _cast; - - public CastAsExpression(Expression expression, Type cast) - { - _expression = expression; - _expression.Parent = this; - - _cast = cast; - } - - internal override Type NodeCheck() => - TypeUtils.JavaScriptTypes.String; - - public override IEnumerator GetEnumerator() - { - yield return _expression; - } - - protected override string NodeRepresentation() => $"as {_cast}"; - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - var castNumber = start; - - if (!_expression.Primary()) - { - instructions.AddRange(_expression.ToInstructions(start, "_t")); - castNumber = instructions.Last().Number + 1; - } - - instructions.Add(new AsString( - "_t" + castNumber, - _expression.Primary() - ? ((PrimaryExpression) _expression).ToValue() - : new Name(instructions.OfType().Last().Left), - castNumber - )); - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs deleted file mode 100644 index 8d555a68..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.ComplexLiterals; - -public class ArrayLiteral : Expression -{ - private readonly List _expressions; - - public ArrayLiteral(IEnumerable expressions) - { - _expressions = new List(expressions); - _expressions.ForEach(expr => expr.Parent = this); - } - - internal override Type NodeCheck() - { - if (_expressions.Any()) - { - var type = _expressions.First().NodeCheck(); - if (_expressions.All(e => e.NodeCheck().Equals(type))) - { - return new ArrayType(type); - } - - throw new WrongArrayLiteralDeclaration(Segment, type); - } - - return TypeUtils.JavaScriptTypes.Undefined; - } - - public override IEnumerator GetEnumerator() => - _expressions.GetEnumerator(); - - protected override string NodeRepresentation() => "[]"; - - public override List ToInstructions(int start, string temp) - { - if (Parent is not AssignmentExpression) - { - temp = "_t" + start; - } - var instructions = new List - { - new CreateArray(start, temp, _expressions.Count) - }; - var j = 1; - for (var i = 0; i < _expressions.Count; i++) - { - var expr = _expressions[i]; - var index = new Constant(i, i.ToString()); - if (expr is PrimaryExpression prim) - { - instructions.Add(new IndexAssignment(temp, (index, prim.ToValue()), start + j)); - j++; - } - else - { - var propInstructions = expr.ToInstructions(start + j, "_t" + (start + j)); - j += propInstructions.Count; - var left = propInstructions.OfType().Last().Left; - propInstructions.Add(new IndexAssignment(temp, (index, new Name(left)), start + j)); - j++; - instructions.AddRange(propInstructions); - } - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs deleted file mode 100644 index 4f59bed5..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.ComplexLiterals; - -public class ObjectLiteral : Expression -{ - private readonly List _properties; - private readonly List _methods; - - public ObjectLiteral(IEnumerable properties, IEnumerable methods) - { - _properties = new List(properties); - _properties.ForEach(prop => prop.Parent = this); - - _methods = new List(methods); - _methods.ForEach(m => m.Parent = this); - } - - internal override Type NodeCheck() - { - var propertyTypes = new List(); - _properties.ForEach(prop => - { - var propType = prop.Expression.NodeCheck(); - propertyTypes.Add(new PropertyType(prop.Id.Id, propType)); - prop.Id.SymbolTable.AddSymbol(propType is ObjectType objectType - ? new ObjectSymbol(prop.Id.Id, objectType) {Table = prop.Expression.SymbolTable} - : new VariableSymbol(prop.Id.Id, propType) - ); - }); - _methods.ForEach(m => - { - var symbol = m.GetSymbol(); - propertyTypes.Add(new PropertyType(symbol.Id, symbol.Type)); - }); - var type = new ObjectType(propertyTypes); - SymbolTable.AddSymbol(new VariableSymbol("this", type, true)); - return type; - } - - public override IEnumerator GetEnumerator() => - _properties.Concat(_methods).GetEnumerator(); - - protected override string NodeRepresentation() => "{}"; - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - _methods.ForEach(method => - { - var mInstructions = method.ToInstructions(start); - instructions.AddRange(mInstructions); - start += mInstructions.Count; - }); - - instructions.Add(new CreateObject(start, temp)); - var i = 1; - foreach (var (id, expr) in _properties) - { - if (expr is PrimaryExpression prim) - { - instructions.Add(new DotAssignment(temp, (new Constant(id, @$"\""{id}\"""), prim.ToValue()), start + i)); - i++; - } - else - { - var propInstructions = expr.ToInstructions(start + i, "_t" + (start + i)); - i += propInstructions.Count; - var left = propInstructions.OfType().Last().Left; - propInstructions.Add(new DotAssignment(temp, (new Constant(id, @$"\""{id}\"""), new Name(left)), start + i)); - i++; - instructions.AddRange(propInstructions); - } - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/Property.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/Property.cs deleted file mode 100644 index ccd379fe..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ComplexLiterals/Property.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.ComplexLiterals; - -public class Property : Expression -{ - public IdentifierReference Id { get; } - public Expression Expression { get; } - - public Property(IdentifierReference id, Expression expression) - { - Id = id; - Id.Parent = this; - - Expression = expression; - Expression.Parent = this; - } - - public void Deconstruct(out string id, out Expression expr) - { - id = Id.Id; - expr = Expression; - } - - public override IEnumerator GetEnumerator() - { - yield return Id; - yield return Expression; - } - - protected override string NodeRepresentation() => ":"; - - public override List ToInstructions(int start, string temp) - { - throw new System.NotImplementedException(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ConditionalExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/ConditionalExpression.cs deleted file mode 100644 index 2eafabaa..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/ConditionalExpression.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class ConditionalExpression : Expression -{ - private readonly Expression _test, _consequent, _alternate; - - public ConditionalExpression(Expression test, Expression consequent, Expression alternate) - { - _test = test; - _consequent = consequent; - _alternate = alternate; - - _test.Parent = this; - _consequent.Parent = this; - _alternate.Parent = this; - } - - internal override Type NodeCheck() - { - var tType = _test.NodeCheck(); - - if (tType.Equals(TypeUtils.JavaScriptTypes.Boolean)) - { - var cType = _consequent.NodeCheck(); - var aType = _alternate.NodeCheck(); - if (cType.Equals(aType)) - { - return cType; - } - - throw new WrongConditionalTypes(_consequent.Segment, cType, _alternate.Segment, aType); - } - - throw new NotBooleanTestExpression(_test.Segment, tType); - } - - public override IEnumerator GetEnumerator() - { - yield return _test; - yield return _consequent; - yield return _alternate; - } - - protected override string NodeRepresentation() => "?:"; - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - IValue ifNotTest; - if (!_test.Primary()) - { - var testInstructions = _test.ToInstructions(start, "_t"); - ifNotTest = new Name(testInstructions.OfType().Last().Left); - instructions.AddRange(testInstructions); - } - else - { - ifNotTest = ((PrimaryExpression) _test).ToValue(); - } - - var cOffset = start + instructions.Count + 1; - var consequentInstructions = _consequent.ToInstructions(cOffset, temp); - - var aOffset = consequentInstructions.Last().Number + 2; - var alternateInstructions = _alternate.ToInstructions(aOffset, temp); - - instructions.Add( - new IfNotGoto( - ifNotTest, alternateInstructions.First().Number, cOffset - 1 - ) - ); - instructions.AddRange(consequentInstructions); - instructions.OfType().Last().Left = temp; - - instructions.Add( - new Goto(alternateInstructions.Last().Number + 1, aOffset - 1) - ); - instructions.AddRange(alternateInstructions); - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/Expression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/Expression.cs deleted file mode 100644 index a4a678a8..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/Expression.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public abstract class Expression : AbstractSyntaxTreeNode -{ - protected Expression() - { - CanEvaluate = true; - } - - public bool Primary() => !this.Any(); - - public abstract List ToInstructions(int start, string temp); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/MemberExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/MemberExpression.cs deleted file mode 100644 index 596f8302..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/MemberExpression.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class MemberExpression : Expression -{ - private readonly IdentifierReference _id; - - public AccessExpression AccessChain { get; } - - public MemberExpression(IdentifierReference id, AccessExpression accessChain) - { - _id = id; - _id.Parent = this; - - AccessChain = accessChain; - if (accessChain != null) - { - AccessChain.Parent = this; - } - } - - public string Id => _id.Id; - - internal override Type NodeCheck() - { - if (AccessChain == null) - { - return _id.NodeCheck(); - } - - var symbol = SymbolTable.FindSymbol(_id.Id); - if (symbol == null) - { - throw new UnknownIdentifierReference(_id); - } - - return AccessChain.Check(symbol.Type); - } - - public override IEnumerator GetEnumerator() - { - if (AccessChain != null) - { - yield return AccessChain; - } - } - - protected override string NodeRepresentation() => Id; - - public override List ToInstructions(int start, string temp) - { - if (AccessChain != null && AccessChain.HasNext()) - { - return AccessChain.ToInstructions(start, _id.Id); - } - - return new(); - } - - public static implicit operator IdentifierReference(MemberExpression member) => - member._id; - - public static explicit operator MemberExpression(IdentifierReference idRef) => - new (idRef, null); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs deleted file mode 100644 index 7fd61bf9..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -public class IdentifierReference : PrimaryExpression -{ - public string Id { get; } - - public IdentifierReference(string id) - { - Id = id; - } - - internal override Type NodeCheck() - { - if (!ChildOf()) - { - var symbol = SymbolTable.FindSymbol(Id); - return symbol switch - { - VariableSymbol v => v.Type, - FunctionSymbol f => f.Type, - _ => throw new UnknownIdentifierReference(this) - }; - } - - return null; - } - - protected override string NodeRepresentation() => Id; - - public override IValue ToValue() => new Name(Id); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs deleted file mode 100644 index b0286598..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -public class Literal : PrimaryExpression -{ - private readonly Type _type; - private readonly object _value; - private readonly string _label; - - public Literal(Type type, object value, Segment segment = null, string label = null) - { - _type = type; - _label = label ?? value.ToString(); - _value = value; - Segment = segment; - } - - internal override Type NodeCheck() => _type; - - protected override string NodeRepresentation() => _label; - - public override IValue ToValue() => new Constant(_value, _label); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs deleted file mode 100644 index 15b785a4..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -public abstract class PrimaryExpression : Expression -{ - public override IEnumerator GetEnumerator() - { - yield break; - } - - public abstract IValue ToValue(); - - public override List ToInstructions(int start, string temp) => - new() - { - new Simple(temp, (null, ToValue()), "", start) - }; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Expressions/UnaryExpression.cs b/Interpreter.Lib/IR/Ast/Nodes/Expressions/UnaryExpression.cs deleted file mode 100644 index 4b2a4a1e..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Expressions/UnaryExpression.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.AccessExpressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Expressions; - -public class UnaryExpression : Expression -{ - private readonly string _operator; - - private readonly Expression _expression; - - public UnaryExpression(string @operator, Expression expression) - { - _operator = @operator; - - _expression = expression; - _expression.Parent = this; - } - - internal override Type NodeCheck() - { - var eType = _expression.NodeCheck(); - Type retType; - if (eType.Equals(TypeUtils.JavaScriptTypes.Number) && _operator == "-") - { - retType = TypeUtils.JavaScriptTypes.Number; - } - else if (eType.Equals(TypeUtils.JavaScriptTypes.Boolean) && _operator == "!") - { - retType = TypeUtils.JavaScriptTypes.Boolean; - } - else if (eType is ArrayType && _operator == "~") - { - retType = TypeUtils.JavaScriptTypes.Number; - } - else throw new UnsupportedOperation(Segment, eType, _operator); - - return retType; - } - - public override IEnumerator GetEnumerator() - { - yield return _expression; - } - - protected override string NodeRepresentation() => _operator; - - public override List ToInstructions(int start, string temp) - { - var instructions = new List(); - - (IValue left, IValue right) right = (null, null); - if (_expression.Primary()) - { - right.right = ((PrimaryExpression) _expression).ToValue(); - } - else - { - instructions.AddRange(_expression.ToInstructions(start, temp)); - if (_expression is MemberExpression member && member.Any()) - { - var i = start + instructions.Count; - var dest = "_t" + i; - var src = instructions.Any() - ? instructions.OfType().Last().Left - : member.Id; - var instruction = member.AccessChain.Tail switch - { - DotAccess dot => new Simple(dest, (new Name(src), new Constant(dot.Id, dot.Id)), ".", i), - IndexAccess index => new Simple(dest, (new Name(src), index.Expression.ToValue()), "[]", i), - _ => throw new NotImplementedException() - }; - instructions.Add(instruction); - } - right.right = new Name(instructions.OfType().Last().Left); - } - - var number = instructions.Any() ? instructions.Last().Number + 1 : start; - - instructions.Add(new Simple( - temp + number, right, _operator, number - )); - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/ScriptBody.cs b/Interpreter.Lib/IR/Ast/Nodes/ScriptBody.cs deleted file mode 100644 index b2fb2be3..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/ScriptBody.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes; - -public class ScriptBody : AbstractSyntaxTreeNode -{ - private readonly List _statementList; - - public ScriptBody(IEnumerable statementList) - { - _statementList = new List(statementList); - _statementList.ForEach(item => item.Parent = this); - } - - public override IEnumerator GetEnumerator() => _statementList.GetEnumerator(); - - protected override string NodeRepresentation() => "Script"; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/StatementListItem.cs b/Interpreter.Lib/IR/Ast/Nodes/StatementListItem.cs deleted file mode 100644 index 7adf45aa..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/StatementListItem.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes; - -public abstract class StatementListItem : AbstractSyntaxTreeNode -{ - public virtual bool IsStatement() => false; - - public virtual bool IsDeclaration() => false; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/BlockStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/BlockStatement.cs deleted file mode 100644 index 004e0ed3..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/BlockStatement.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class BlockStatement : Statement -{ - private readonly List _statementList; - - public BlockStatement(IEnumerable statementList) - { - _statementList = new List(statementList); - _statementList.ForEach(item => item.Parent = this); - } - - public bool HasReturnStatement() - { - var has = _statementList.Any(item => item is ReturnStatement); - if (!has) - { - has = _statementList - .Where(item => item.IsStatement()) - .OfType() - .Any(ifStmt => ifStmt.HasReturnStatement()); - } - - return has; - } - - public override IEnumerator GetEnumerator() => _statementList.GetEnumerator(); - - protected override string NodeRepresentation() => "{}"; - - public override List ToInstructions(int start) - { - var blockInstructions = new List(); - var offset = start; - foreach (var item in _statementList) - { - var itemInstructions = item.ToInstructions(offset); - blockInstructions.AddRange(itemInstructions); - if (item is ReturnStatement) - { - break; - } - - offset += itemInstructions.Count; - } - - return blockInstructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/BreakStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/BreakStatement.cs deleted file mode 100644 index c1cfb42b..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/BreakStatement.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class BreakStatement : InsideLoopStatement -{ - protected override string NodeRepresentation() => "break"; - - public override List ToInstructions(int start) => - new() - { - new Goto(-1, start) - }; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/ContinueStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/ContinueStatement.cs deleted file mode 100644 index d4374d10..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/ContinueStatement.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class ContinueStatement : InsideLoopStatement -{ - protected override string NodeRepresentation() => "continue"; - - public override List ToInstructions(int start) => - new() - { - new Goto(-2, start) - }; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/ExpressionStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/ExpressionStatement.cs deleted file mode 100644 index bba3d9b0..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/ExpressionStatement.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class ExpressionStatement : Statement -{ - private readonly Expression _expression; - - public ExpressionStatement(Expression expression) - { - _expression = expression; - expression.Parent = this; - } - - public override IEnumerator GetEnumerator() - { - yield return _expression; - } - - protected override string NodeRepresentation() => nameof(ExpressionStatement); - - public override List ToInstructions(int start) => _expression.ToInstructions(start); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/IfStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/IfStatement.cs deleted file mode 100644 index f18fe08c..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/IfStatement.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class IfStatement : Statement -{ - private readonly Expression _test; - private readonly Statement _then; - private readonly Statement _else; - - public IfStatement(Expression test, Statement then, Statement @else = null) - { - _test = test; - _test.Parent = this; - - _then = then; - _then.Parent = this; - - if (@else != null) - { - _else = @else; - _else.Parent = this; - } - - CanEvaluate = true; - } - - public bool HasReturnStatement() - { - var thenResult = _then is ReturnStatement; - if (!thenResult) - { - if (_then is BlockStatement block) - { - thenResult = block.HasReturnStatement(); - } - } - - var elseResult = _else == null || _else is ReturnStatement; - if (!elseResult) - { - if (_else is BlockStatement block) - { - elseResult = block.HasReturnStatement(); - } - } - - return thenResult && elseResult; - } - - public override IEnumerator GetEnumerator() - { - yield return _test; - yield return _then; - if (_else != null) - { - yield return _else; - } - } - - internal override Type NodeCheck() - { - var testType = _test.NodeCheck(); - if (!testType.Equals(TypeUtils.JavaScriptTypes.Boolean)) - { - throw new NotBooleanTestExpression(Segment, testType); - } - - return testType; - } - - protected override string NodeRepresentation() => "if"; - - public override List ToInstructions(int start) - { - var instructions = new List(); - if (_then.Any() && (_else == null || _else.Any())) - { - IValue ifNotTest; - if (!_test.Primary()) - { - var testInstructions = _test.ToInstructions(start, "_t"); - ifNotTest = new Name(testInstructions.OfType().Last().Left); - instructions.AddRange(testInstructions); - } - else - { - ifNotTest = ((PrimaryExpression) _test).ToValue(); - } - - var tOffset = start + instructions.Count + 1; - var thenInstructions = _then.ToInstructions(tOffset); - - var eOffset = thenInstructions.Any() - ? thenInstructions.Last().Number + 2 - : tOffset + 1; - var elseInstructions = _else?.ToInstructions(eOffset); - - instructions.Add( - new IfNotGoto( - ifNotTest, elseInstructions?.First().Number ?? eOffset - 1, tOffset - 1 - ) - ); - - instructions.AddRange(thenInstructions); - - if (elseInstructions != null) - { - instructions.Add( - new Goto( - elseInstructions.Last().Number + 1, - eOffset - 1 - ) - ); - instructions.AddRange(elseInstructions); - } - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/InsideLoopStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/InsideLoopStatement.cs deleted file mode 100644 index 77744c3a..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/InsideLoopStatement.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Exceptions; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public abstract class InsideLoopStatement : Statement -{ - protected InsideLoopStatement() - { - CanEvaluate = true; - } - - internal override Type NodeCheck() - { - if (!ChildOf()) - { - throw new OutsideOfLoop(Segment, NodeRepresentation()); - } - return null; - } - - public override IEnumerator GetEnumerator() - { - yield break; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/ReturnStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/ReturnStatement.cs deleted file mode 100644 index a69cb44f..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/ReturnStatement.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class ReturnStatement : Statement -{ - private readonly Expression _expression; - - public ReturnStatement(Expression expression = null) - { - _expression = expression; - CanEvaluate = true; - if (expression != null) - { - _expression.Parent = this; - } - } - - internal override Type NodeCheck() - { - if (!ChildOf()) - { - throw new ReturnOutsideFunction(Segment); - } - - return _expression?.NodeCheck() ?? TypeUtils.JavaScriptTypes.Void; - } - - public override IEnumerator GetEnumerator() - { - if (_expression == null) - { - yield break; - } - - yield return _expression; - } - - protected override string NodeRepresentation() => "return"; - - private FunctionSymbol GetCallee() - { - var parent = Parent; - while (parent != null) - { - if (parent is FunctionDeclaration declaration) - { - return declaration.GetSymbol(); - } - - parent = parent.Parent; - } - - return null; - } - - public override List ToInstructions(int start) - { - var instructions = new List(); - if (_expression == null) - { - instructions.Add(new Return(GetCallee().CallInfo.Location, start)); - } - else - { - if (_expression.Primary()) - { - instructions.Add(new Return( - GetCallee().CallInfo.Location, start, ((PrimaryExpression) _expression).ToValue()) - ); - } - else - { - var eInstructions = _expression.ToInstructions(start, "_t"); - var last = eInstructions.OfType().Last(); - instructions.AddRange(eInstructions); - instructions.Add(new Return( - GetCallee().CallInfo.Location, last.Number + 1, new Name(last.Left) - )); - } - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/Statement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/Statement.cs deleted file mode 100644 index 2a4f7f0d..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/Statement.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public abstract class Statement : StatementListItem -{ - public override bool IsStatement() => true; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/TypeStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/TypeStatement.cs deleted file mode 100644 index 07563b27..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/TypeStatement.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class TypeStatement : Statement -{ - private readonly string _typeId; - private readonly Type _typeValue; - - public TypeStatement(string typeId, Type typeValue) - { - _typeId = typeId; - _typeValue = typeValue; - } - - public override IEnumerator GetEnumerator() - { - yield break; - } - - protected override string NodeRepresentation() => - $"type {_typeId} = {_typeValue}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/Ast/Nodes/Statements/WhileStatement.cs b/Interpreter.Lib/IR/Ast/Nodes/Statements/WhileStatement.cs deleted file mode 100644 index 77f4e052..00000000 --- a/Interpreter.Lib/IR/Ast/Nodes/Statements/WhileStatement.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.Ast.Nodes.Statements; - -public class WhileStatement : Statement -{ - private readonly Expression _condition; - private readonly Statement _statement; - - public WhileStatement(Expression condition, Statement statement) - { - _condition = condition; - _condition.Parent = this; - - _statement = statement; - _statement.Parent = this; - - CanEvaluate = true; - } - - public override IEnumerator GetEnumerator() - { - yield return _condition; - yield return _statement; - } - - internal override Type NodeCheck() - { - var condType = _condition.NodeCheck(); - if (!condType.Equals(TypeUtils.JavaScriptTypes.Boolean)) - { - throw new NotBooleanTestExpression(Segment, condType); - } - - return condType; - } - - protected override string NodeRepresentation() => "while"; - - public override List ToInstructions(int start) - { - var instructions = new List(); - IValue ifNotTest; - if (!_condition.Primary()) - { - var conditionInstructions = _condition.ToInstructions(start, "_t"); - ifNotTest = new Name(conditionInstructions.OfType().Last().Left); - instructions.AddRange(conditionInstructions); - } - else - { - ifNotTest = ((PrimaryExpression) _condition).ToValue(); - } - - var cOffset = start + instructions.Count + 1; - var loopBody = _statement.ToInstructions(cOffset); - if (loopBody.Any()) - { - instructions.Add(new IfNotGoto(ifNotTest, loopBody.Last().Number + 2, cOffset - 1)); - instructions.AddRange(loopBody); - instructions.Add(new Goto(start, loopBody.Last().Number + 1)); - - loopBody - .OfType() - .Where(g => g.Jump() < 0) - .ToList() - .ForEach(j => - { - if (j.Jump() == -1) - { - j.SetJump(loopBody.Last().Number + 2); - } - else if (j.Jump() == -2) - { - j.SetJump(start); - } - }); - } - else - { - instructions.Clear(); - } - - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ArrayAccessException.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/ArrayAccessException.cs deleted file mode 100644 index bd176e61..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ArrayAccessException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class ArrayAccessException : SemanticException -{ - public ArrayAccessException(Segment segment, Type type) : - base(segment, $"Array element cannot be accessed with type {type} it must be of type number") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/AssignmentToConst.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/AssignmentToConst.cs deleted file mode 100644 index 353b87ea..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/AssignmentToConst.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class AssignmentToConst : SemanticException -{ - public AssignmentToConst(IdentifierReference ident) : - base(ident.Segment,$"Cannot assign to const: {ident.Id}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/CannotDefineType.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/CannotDefineType.cs deleted file mode 100644 index 2a2dd640..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/CannotDefineType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class CannotDefineType : SemanticException -{ - public CannotDefineType(Segment segment) : - base(segment, "Cannot define type") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ConstWithoutInitializer.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/ConstWithoutInitializer.cs deleted file mode 100644 index 9d451dd1..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ConstWithoutInitializer.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class ConstWithoutInitializer : SemanticException -{ - public ConstWithoutInitializer(IdentifierReference ident) : - base(ident.Segment, $"Const without initializer: {ident.Id}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/DeclarationAlreadyExists.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/DeclarationAlreadyExists.cs deleted file mode 100644 index ddb751f9..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/DeclarationAlreadyExists.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class DeclarationAlreadyExists : SemanticException -{ - public DeclarationAlreadyExists(IdentifierReference ident) : - base(ident.Segment, $"Declaration already exists: {ident.Id}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/FunctionWithoutReturnStatement.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/FunctionWithoutReturnStatement.cs deleted file mode 100644 index 09098d33..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/FunctionWithoutReturnStatement.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class FunctionWithoutReturnStatement : SemanticException -{ - public FunctionWithoutReturnStatement(Segment segment) : - base(segment, "function with non-void return type must have a return statement") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/IncompatibleTypesOfOperands.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/IncompatibleTypesOfOperands.cs deleted file mode 100644 index d896c792..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/IncompatibleTypesOfOperands.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class IncompatibleTypesOfOperands : SemanticException -{ - public IncompatibleTypesOfOperands(Segment segment, Type left, Type right) : - base(segment, $"Incompatible types of operands: {left} and {right}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/NotBooleanTestExpression.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/NotBooleanTestExpression.cs deleted file mode 100644 index a1940bb2..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/NotBooleanTestExpression.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class NotBooleanTestExpression : SemanticException -{ - public NotBooleanTestExpression(Segment segment, Type type) : - base(segment, $"Type of expression is {type} but expected boolean") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ObjectAccessException.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/ObjectAccessException.cs deleted file mode 100644 index ae459389..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ObjectAccessException.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class ObjectAccessException : SemanticException -{ - public ObjectAccessException(Segment segment, ObjectType objectType, string field) : - base(segment, $"Object type {objectType} has no field {field}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/OutsideOfLoop.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/OutsideOfLoop.cs deleted file mode 100644 index 2049c6cf..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/OutsideOfLoop.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class OutsideOfLoop : SemanticException -{ - public OutsideOfLoop(Segment segment, string keyword) : - base(segment, $"\"{keyword}\" outside of loop") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ReturnOutsideFunction.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/ReturnOutsideFunction.cs deleted file mode 100644 index bd27215e..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/ReturnOutsideFunction.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class ReturnOutsideFunction : SemanticException -{ - public ReturnOutsideFunction(Segment segment) : - base(segment, "\"return\" outside function") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/SemanticException.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/SemanticException.cs deleted file mode 100644 index 1571a424..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/SemanticException.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -[Serializable] -public abstract class SemanticException : Exception -{ - protected SemanticException() { } - - protected SemanticException(string message) : base(message) { } - - protected SemanticException(string message, Exception inner) : base(message, inner) { } - - protected SemanticException(Segment segment, string message) : - base($"{segment} {message}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/SymbolIsNotCallable.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/SymbolIsNotCallable.cs deleted file mode 100644 index 5f3609e5..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/SymbolIsNotCallable.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class SymbolIsNotCallable: SemanticException -{ - public SymbolIsNotCallable(string symbol, Segment segment) : - base(segment, $"Symbol is not callable: {symbol}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnknownIdentifierReference.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnknownIdentifierReference.cs deleted file mode 100644 index 99397033..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnknownIdentifierReference.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class UnknownIdentifierReference : SemanticException -{ - public UnknownIdentifierReference(IdentifierReference ident) : - base(ident.Segment, $"Unknown identifier reference: {ident.Id}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnsupportedOperation.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnsupportedOperation.cs deleted file mode 100644 index e6194f7f..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/UnsupportedOperation.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class UnsupportedOperation : SemanticException -{ - public UnsupportedOperation(Segment segment, Type type, string @operator) : - base(segment, $"Type {type} does not support operation {@operator}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongArrayLiteralDeclaration.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongArrayLiteralDeclaration.cs deleted file mode 100644 index 63b09d55..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongArrayLiteralDeclaration.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class WrongArrayLiteralDeclaration : SemanticException -{ - public WrongArrayLiteralDeclaration(Segment segment, Type type) : - base(segment, $"{segment} Wrong array literal declaration: all array elements must be of type {type}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongConditionalTypes.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongConditionalTypes.cs deleted file mode 100644 index eb2aaa3e..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongConditionalTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class WrongConditionalTypes : SemanticException -{ - public WrongConditionalTypes(Segment cSegment, Type cType, Segment aSegment, Type aType) : - base(cSegment + aSegment, $"Different types in conditional: {cSegment} consequent - {cType}, {aSegment} alternate {aType}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongNumberOfArguments.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongNumberOfArguments.cs deleted file mode 100644 index 630cc175..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongNumberOfArguments.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class WrongNumberOfArguments : SemanticException -{ - public WrongNumberOfArguments(Segment segment, int expected, int actual) : - base(segment, $"Wrong number of arguments: expected {expected}, actual {actual}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongReturnType.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongReturnType.cs deleted file mode 100644 index 203ad081..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongReturnType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class WrongReturnType : SemanticException -{ - public WrongReturnType(Segment segment, Type expected, Type actual) : - base(segment, $"Wrong return type: expected {expected}, actual {actual}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongTypeOfArgument.cs b/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongTypeOfArgument.cs deleted file mode 100644 index 6b34e8c2..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Exceptions/WrongTypeOfArgument.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Lib.IR.CheckSemantics.Exceptions; - -public class WrongTypeOfArgument : SemanticException -{ - public WrongTypeOfArgument(Segment segment, Type expected, Type actual) : - base(segment,$"Wrong type of argument: expected {expected}, actual {actual}") { } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/Any.cs b/Interpreter.Lib/IR/CheckSemantics/Types/Any.cs deleted file mode 100644 index ff982fbc..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/Any.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class Any : Type -{ - public Any() : base("any") - { - } - - public override bool Equals(object obj) => true; - - public override int GetHashCode() => "any".GetHashCode(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/ArrayType.cs b/Interpreter.Lib/IR/CheckSemantics/Types/ArrayType.cs deleted file mode 100644 index 5ba7fdee..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/ArrayType.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Types.Visitors; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class ArrayType : Type -{ - public Type Type { get; set; } - - public ArrayType(Type type) : base($"{type}[]") - { - Type = type; - } - - public override Unit Accept(ReferenceResolver visitor) => - visitor.Visit(this); - - public override string Accept(ObjectTypePrinter visitor) => - visitor.Visit(this); - - public override int Accept(ObjectTypeHasher visitor) => - visitor.Visit(this); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) return true; - if (obj == null || GetType() != obj.GetType()) return false; - var that = (ArrayType) obj; - return Equals(Type, that.Type); - } - - public override int GetHashCode() => - // ReSharper disable once NonReadonlyMemberInGetHashCode - Type.GetHashCode(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/FunctionType.cs b/Interpreter.Lib/IR/CheckSemantics/Types/FunctionType.cs deleted file mode 100644 index 98f3296b..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/FunctionType.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text; -using Interpreter.Lib.IR.CheckSemantics.Types.Visitors; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class FunctionType : Type -{ - public Type ReturnType { get; set; } - - public List Arguments { get; } - - public FunctionType(Type returnType, IEnumerable arguments) - { - ReturnType = returnType; - Arguments = new List(arguments); - } - - public override Unit Accept(ReferenceResolver visitor) => - visitor.Visit(this); - - public override string Accept(ObjectTypePrinter visitor) => - visitor.Visit(this); - - public override int Accept(ObjectTypeHasher visitor) => - visitor.Visit(this); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) return true; - if (obj == null || GetType() != obj.GetType()) return false; - var that = (FunctionType) obj; - return ReturnType.Equals(that.ReturnType) && - Arguments.Count == that.Arguments.Count && - Arguments.Zip(that.Arguments) - .All(pair => pair.First.Equals(pair.Second)); - } - - public override int GetHashCode() => - HashCode.Combine( - // ReSharper disable once NonReadonlyMemberInGetHashCode - ReturnType, - Arguments - .Select(arg => arg.GetHashCode()) - .Aggregate(36, HashCode.Combine) - ); - - public override string ToString() => - new StringBuilder() - .Append('(') - .AppendJoin(", ", Arguments) - .Append(')') - .Append(" => ") - .Append(ReturnType) - .ToString(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/NullType.cs b/Interpreter.Lib/IR/CheckSemantics/Types/NullType.cs deleted file mode 100644 index 8d78a520..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/NullType.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class NullType : Type -{ - public NullType() : base("null") - { - } - - public override bool Equals(object obj) - { - return obj is NullableType or NullType; - } - - public override int GetHashCode() => - "null".GetHashCode(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/NullableType.cs b/Interpreter.Lib/IR/CheckSemantics/Types/NullableType.cs deleted file mode 100644 index 489d0a4e..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/NullableType.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Types.Visitors; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class NullableType : Type -{ - public Type Type { get; set; } - - public NullableType(Type type) : base($"{type}?") - { - Type = type; - } - - protected NullableType() - { - } - - public override Unit Accept(ReferenceResolver visitor) => - visitor.Visit(this); - - public override string Accept(ObjectTypePrinter visitor) => - visitor.Visit(this); - - public override int Accept(ObjectTypeHasher visitor) => - visitor.Visit(this); - - public override bool Equals(object obj) - { - if (obj is NullableType that) - { - return Type.Equals(that.Type); - } - return obj is NullType; - } - - public override int GetHashCode() => - // ReSharper disable once NonReadonlyMemberInGetHashCode - Type.GetHashCode(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/ObjectType.cs b/Interpreter.Lib/IR/CheckSemantics/Types/ObjectType.cs deleted file mode 100644 index 57f30446..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/ObjectType.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Types.Visitors; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class ObjectType : NullableType -{ - private readonly Dictionary _properties; - private readonly ObjectTypeHasher _hasher; - private readonly ObjectTypePrinter _serializer; - - public ObjectType(IEnumerable properties) - { - _properties = properties - .OrderBy(x => x.Id) - .ToDictionary( - x => x.Id, - x => x.Type - ); - _hasher = new ObjectTypeHasher(this); - _serializer = new ObjectTypePrinter(this); - } - - public Type this[string id] - { - get => _properties.ContainsKey(id) - ? _properties[id] - : null; - set => _properties[id] = value; - } - - public IEnumerable Keys => _properties.Keys; - - public void ResolveSelfReferences(string self) => - new ReferenceResolver(this, self) - .Visit(this); - - public override Unit Accept(ReferenceResolver visitor) => - visitor.Visit(this); - - public override string Accept(ObjectTypePrinter visitor) => - visitor.Visit(this); - - public override int Accept(ObjectTypeHasher visitor) => - visitor.Visit(this); - - public override bool Equals(object obj) - { - if (obj is ObjectType that) - { - return ReferenceEquals(this, that) || _properties.Count == that._properties.Count && - _properties - .Zip(that._properties) - .All(pair => - pair.First.Key == pair.Second.Key && - pair.First.Value.Equals(pair.Second.Value) - ); - } - - return obj is NullType; - } - - public override int GetHashCode() => - _hasher.Visit(this); - - public override string ToString() => - _serializer.Visit(this); -} - -public record PropertyType(string Id, Type Type); \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/Type.cs b/Interpreter.Lib/IR/CheckSemantics/Types/Type.cs deleted file mode 100644 index 4b1d7aa5..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/Type.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Types.Visitors; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public class Type : - IVisitable, - IVisitable, - IVisitable -{ - private readonly string _name; - - protected Type() - { - } - - public Type(string name) => _name = name; - - public bool Recursive { get; set; } - - public virtual Unit Accept(ReferenceResolver visitor) => - visitor.Visit(this); - - public virtual string Accept(ObjectTypePrinter visitor) => - visitor.Visit(this); - - public virtual int Accept(ObjectTypeHasher visitor) => - visitor.Visit(this); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) return true; - if (obj == null || GetType() != obj.GetType()) return false; - var that = (Type) obj; - return Equals(_name, that._name); - } - - public override int GetHashCode() => - _name.GetHashCode(); - - public override string ToString() => _name; - - public static implicit operator Type(string alias) => new(alias); - - public static bool operator ==(Type left, Type right) => Equals(left, right); - - public static bool operator !=(Type left, Type right) => !(left == right); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/TypeUtils.cs b/Interpreter.Lib/IR/CheckSemantics/Types/TypeUtils.cs deleted file mode 100644 index 7f78bc92..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/TypeUtils.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Interpreter.Lib.IR.CheckSemantics.Types; - -public static class TypeUtils -{ - public static ( - Type Number, Type Boolean, Type String, Type Null, Type Undefined, Type Void - ) JavaScriptTypes { get; } = ( - new Type("number"), - new Type("boolean"), - new Type("string"), - new NullType(), - new Type("undefined"), - new Type("void") - ); - - public static object GetDefaultValue(Type type) - { - if (type.Equals(JavaScriptTypes.Boolean)) - return false; - if (type.Equals(JavaScriptTypes.Number)) - return 0; - if (type.Equals(JavaScriptTypes.String)) - return ""; - if (type.Equals(JavaScriptTypes.Void)) - return new Void(); - if (type.Equals(JavaScriptTypes.Null)) - return null; - if (type is ArrayType) - return new List(); - - return new Undefined(); - } - - public struct Undefined - { - public override string ToString() => JavaScriptTypes.Undefined.ToString(); - } - - private struct Void - { - public override string ToString() => JavaScriptTypes.Void.ToString(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypeHasher.cs b/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypeHasher.cs deleted file mode 100644 index ef33b5ef..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypeHasher.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types.Visitors; - -public class ObjectTypeHasher : - IVisitor, - IVisitor, - IVisitor, - IVisitor, - IVisitor -{ - private readonly ObjectType _reference; - - public ObjectTypeHasher(ObjectType reference) => - _reference = reference; - - public int Visit(Type visitable) => - visitable.GetHashCode(); - - public int Visit(ObjectType visitable) => - visitable.Keys.Select(key => HashCode.Combine(key, - visitable[key].Equals(_reference) - ? "@this".GetHashCode() - : visitable[key].Recursive - ? key.GetHashCode() - : visitable[key].Accept(this)) - ).Aggregate(36, HashCode.Combine); - - public int Visit(ArrayType visitable) => - visitable.Type.Equals(_reference) - ? "@this".GetHashCode() - : visitable.Type.Accept(this); - - public int Visit(NullableType visitable) => - visitable.Type.Equals(_reference) - ? "@this".GetHashCode() - : visitable.Type.Accept(this); - - public int Visit(FunctionType visitable) => - HashCode.Combine( - visitable.ReturnType.Equals(_reference) - ? "@this".GetHashCode() - : visitable.ReturnType.Accept(this), - visitable.Arguments.Select(arg => - arg.Equals(_reference) - ? "@this".GetHashCode() - : arg.Accept(this) - ).Aggregate(36, HashCode.Combine)); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypePrinter.cs b/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypePrinter.cs deleted file mode 100644 index 1f11d91a..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ObjectTypePrinter.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Text; -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types.Visitors; - -public class ObjectTypePrinter : - IVisitor, - IVisitor, - IVisitor, - IVisitor, - IVisitor -{ - private readonly ObjectType _reference; - - public ObjectTypePrinter(ObjectType reference) => - _reference = reference; - - public string Visit(Type visitable) => - visitable.ToString(); - - public string Visit(ObjectType visitable) - { - var sb = new StringBuilder("{"); - foreach (var key in visitable.Keys) - { - var type = visitable[key]; - var prop = $"{key}: "; - prop += type.Equals(_reference) - ? "@this" - : type.Recursive - ? key - : type.Accept(this); - sb.Append(prop).Append(';'); - } - - return sb.Append('}').ToString(); - } - - public string Visit(ArrayType visitable) - { - var sb = new StringBuilder(); - sb.Append(visitable.Type.Equals(_reference) - ? "@this" - : visitable.Type.Accept(this) - ); - - return sb.Append("[]").ToString(); - } - - public string Visit(NullableType visitable) - { - var sb = new StringBuilder(); - sb.Append(visitable.Type.Equals(_reference) - ? "@this" - : visitable.Type.Accept(this) - ); - - return sb.Append('?').ToString(); - } - - public string Visit(FunctionType visitable) - { - var sb = new StringBuilder("("); - sb.AppendJoin(", ", visitable.Arguments.Select(x => x.Equals(_reference) - ? "@this" - : x.Accept(this) - )).Append(") => "); - sb.Append(visitable.ReturnType.Equals(_reference) - ? "@this" - : visitable.ReturnType.Accept(this) - ); - - return sb.ToString(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ReferenceResolver.cs b/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ReferenceResolver.cs deleted file mode 100644 index 483654f0..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Types/Visitors/ReferenceResolver.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Visitor.NET.Lib.Core; - -namespace Interpreter.Lib.IR.CheckSemantics.Types.Visitors; - -public class ReferenceResolver : - IVisitor, - IVisitor, - IVisitor, - IVisitor, - IVisitor -{ - private readonly ObjectType _reference; - private readonly string _refId; - private readonly HashSet _visited; - - public ReferenceResolver(ObjectType reference, string refId) - { - _reference = reference; - _refId = refId; - _visited = new(); - } - - public Unit Visit(ObjectType visitable) - { - if (_visited.Contains(visitable)) - return default; - _visited.Add(visitable); - - foreach (var key in visitable.Keys) - if (_refId == visitable[key]) - visitable[key] = _reference; - else - visitable[key].Accept(this); - return default; - } - - public Unit Visit(Type visitable) => default; - - public Unit Visit(ArrayType visitable) - { - if (visitable.Type == _refId) - visitable.Type = _reference; - else - visitable.Type.Accept(this); - return default; - } - - public Unit Visit(FunctionType visitable) - { - if (visitable.ReturnType == _refId) - visitable.ReturnType = _reference; - else - visitable.ReturnType.Accept(this); - - for (var i = 0; i < visitable.Arguments.Count; i++) - { - var argType = visitable.Arguments[i]; - if (argType == _refId) - visitable.Arguments[i] = _reference; - else - argType.Accept(this); - } - - return default; - } - - public Unit Visit(NullableType visitable) - { - if (visitable.Type == _refId) - visitable.Type = _reference; - else - visitable.Type.Accept(this); - return default; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTable.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTable.cs deleted file mode 100644 index 2a7d33f4..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTable.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.CheckSemantics.Variables; - -public class SymbolTable -{ - private readonly Dictionary _symbols = new(); - private readonly Dictionary _types = new(); - - private SymbolTable _openScope; - - public void AddOpenScope(SymbolTable table) - { - _openScope = table; - } - - public void AddSymbol(Symbol symbol) => _symbols[symbol.Id] = symbol; - - public void AddType(Type type, string typeId = null) => - _types[typeId ?? type.ToString()] = type; - - public Type FindType(string typeId) - { - var hasInsideTheScope = _types.TryGetValue(typeId, out var type); - return !hasInsideTheScope ? _openScope?.FindType(typeId) : type; - } - - /// - /// Поиск эффективного символа - /// - public T FindSymbol(string id) where T : Symbol - { - var hasInsideTheScope = _symbols.TryGetValue(id, out var symbol); - return !hasInsideTheScope ? _openScope?.FindSymbol(id) : symbol as T; - } - - /// - /// Проверяет наличие собственного символа - /// - public bool ContainsSymbol(string id) => _symbols.ContainsKey(id); - - public void Clear() => _symbols.Clear(); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTableUtils.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTableUtils.cs deleted file mode 100644 index b9b228da..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/SymbolTableUtils.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.IR.Ast.Nodes; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Statements; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -namespace Interpreter.Lib.IR.CheckSemantics.Variables; - -public static class SymbolTableUtils -{ - public static SymbolTable GetStandardLibrary() - { - var library = new SymbolTable(); - - library.AddType(TypeUtils.JavaScriptTypes.Number); - library.AddType(TypeUtils.JavaScriptTypes.Boolean); - library.AddType(TypeUtils.JavaScriptTypes.String); - library.AddType(TypeUtils.JavaScriptTypes.Null); - library.AddType(TypeUtils.JavaScriptTypes.Void); - - var print = new FunctionSymbol( - "print", - new List - { - new VariableSymbol("str", TypeUtils.JavaScriptTypes.String) - }, - new FunctionType(TypeUtils.JavaScriptTypes.Void, new[] {TypeUtils.JavaScriptTypes.String}) - ); - print.Body = new FunctionDeclaration( - print, - new BlockStatement(new List()) - { - SymbolTable = new SymbolTable() - } - ) - { - SymbolTable = new SymbolTable(), - Segment = new Segment( - new Coordinates(0, 0), - new Coordinates(0, 0) - ) - }; - - library.AddSymbol(print); - - var symbolTable = new SymbolTable(); - symbolTable.AddOpenScope(library); - return symbolTable; - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/FunctionSymbol.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/FunctionSymbol.cs deleted file mode 100644 index a7144623..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/FunctionSymbol.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text; -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -public class FunctionSymbol : Symbol -{ - public override FunctionType Type { get; } - - public List Parameters { get; } - - public FunctionDeclaration Body { get; set; } - - public FunctionInfo CallInfo { get; } - - public FunctionSymbol(string id, IEnumerable parameters, FunctionType type) : - base(id, type) - { - Parameters = new List(parameters); - CallInfo = new FunctionInfo(id); - Type = type; - } - - public override string ToString() - { - var sb = new StringBuilder($"function {Id}("); - sb.AppendJoin(',', Parameters); - sb.Append($") => {Type.ReturnType}"); - return sb.ToString(); - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/ObjectSymbol.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/ObjectSymbol.cs deleted file mode 100644 index efb9672e..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/ObjectSymbol.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Types; - -namespace Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -public class ObjectSymbol : VariableSymbol -{ - public override ObjectType Type { get; } - - public SymbolTable Table { get; init; } - - public ObjectSymbol(string id, ObjectType objectType, bool readOnly = false, SymbolTable table = null) : - base(id, objectType, readOnly) - { - Type = objectType; - if (table != null) - { - foreach (var key in objectType.Keys) - { - if (objectType[key] is FunctionType) - { - var function = table.FindSymbol(key); - function.CallInfo.MethodOf = id; - } - } - } - } -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/Symbol.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/Symbol.cs deleted file mode 100644 index 87b487c8..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/Symbol.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -public abstract class Symbol -{ - // ReSharper disable once VirtualMemberNeverOverridden.Global - public virtual string Id { get; } - - public virtual Type Type { get; } - - protected Symbol(string id, Type type) => - (Id, Type) = (id, type); -} \ No newline at end of file diff --git a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/VariableSymbol.cs b/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/VariableSymbol.cs deleted file mode 100644 index 9ef2db85..00000000 --- a/Interpreter.Lib/IR/CheckSemantics/Variables/Symbols/VariableSymbol.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; - -public class VariableSymbol : Symbol -{ - public bool ReadOnly { get; } - - public VariableSymbol(string id, Type type, bool readOnly = false) : - base(id, type) => - ReadOnly = readOnly; - - public override string ToString() => $"{(ReadOnly ? "const " : "")}{Id}: {Type}"; -} \ No newline at end of file diff --git a/Interpreter.Lib/Interpreter.Lib.csproj b/Interpreter.Lib/Interpreter.Lib.csproj deleted file mode 100644 index e15b082f..00000000 --- a/Interpreter.Lib/Interpreter.Lib.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net7.0 - enable - - - - - - - diff --git a/Interpreter.Tests/GlobalUsings.cs b/Interpreter.Tests/GlobalUsings.cs deleted file mode 100644 index 9f96c142..00000000 --- a/Interpreter.Tests/GlobalUsings.cs +++ /dev/null @@ -1,3 +0,0 @@ -// Global using directives - -global using Type = Interpreter.Lib.IR.CheckSemantics.Types.Type; \ No newline at end of file diff --git a/Interpreter.Tests/MockExtensions.cs b/Interpreter.Tests/MockExtensions.cs deleted file mode 100644 index 1b88106b..00000000 --- a/Interpreter.Tests/MockExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.BackEnd.Instructions; -using Microsoft.Extensions.Options; -using Moq; - -namespace Interpreter.Tests; - -public static class MockExtensions -{ - public static Mock Trackable(this Mock halt) - { - halt.Setup(x => x.Execute(It.IsAny())) - .Returns(-3).Verifiable(); - halt.Setup(x => x.End()).Returns(true); - return halt; - } - - public static IOptions ToOptions - (this Mock commandLineSettings) => - Options.Create(commandLineSettings.Object); -} \ No newline at end of file diff --git a/Interpreter.Tests/Stubs/SemanticExceptionStub.cs b/Interpreter.Tests/Stubs/SemanticExceptionStub.cs deleted file mode 100644 index c1cc2463..00000000 --- a/Interpreter.Tests/Stubs/SemanticExceptionStub.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Interpreter.Lib.IR.CheckSemantics.Exceptions; - -namespace Interpreter.Tests.Stubs; - -public class SemanticExceptionStub : SemanticException { } \ No newline at end of file diff --git a/Interpreter.Tests/TestData/InstructionsData.cs b/Interpreter.Tests/TestData/InstructionsData.cs deleted file mode 100644 index 2eb25f8b..00000000 --- a/Interpreter.Tests/TestData/InstructionsData.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections; -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; - -namespace Interpreter.Tests.TestData; - -public class InstructionsData : IEnumerable -{ - public IEnumerator GetEnumerator() - { - yield return new object[] - { - new AsString("str", new Name("num"), 0), - "0: str = num as string" - }; - yield return new object[] - { - new BeginFunction(1, new FunctionInfo("func", 1)), - "1: BeginFunction func" - }; - yield return new object[] - { - new CallFunction(new FunctionInfo("func"), 2, 0), - "2: Call (0, func), 0" - }; - yield return new object[] - { - new CallFunction(new FunctionInfo("func"), 2, 0, "ret"), - "2: ret = Call (0, func), 0" - }; - yield return new object[] - { - new CreateArray(3, "arr", 5), - "3: array arr = [5]" - }; - yield return new object[] - { - new CreateObject(4, "obj"), - "4: object obj = {}" - }; - yield return new object[] - { - new DotAssignment("obj", (new Constant("prop", "prop"), new Constant(3, "3")), 5), - "5: obj.prop = 3" - }; - yield return new object[] - { - new Goto(10, 6), - "6: Goto 10" - }; - yield return new object[] - { - new Halt(7), - "7: End" - }; - yield return new object[] - { - new IfNotGoto(new Name("test"), 17, 8), - "8: IfNot test Goto 17" - }; - yield return new object[] - { - new IndexAssignment("arr", (new Constant(1, "1"), new Constant(1, "1")), 9), - "9: arr[1] = 1" - }; - yield return new object[] - { - new Print(10, new Name("str")), - "10: Print str" - }; - yield return new object[] - { - new PushParameter(11, "param", new Name("value")), - "11: PushParameter param = value" - }; - yield return new object[] - { - new RemoveFromArray(12, "arr", new Constant(0, "0")), - "12: RemoveFrom arr at 0" - }; - yield return new object[] - { - new Return(3, 13), - "13: Return" - }; - yield return new object[] - { - new Return(3, 13, new Name("result")), - "13: Return result" - }; - yield return new object[] - { - new Simple("a", (new Name("b"), new Name("c")), "+", 14), - "14: a = b + c" - }; - yield return new object[] - { - new Simple("b", (null, new Name("c")), "-", 14), - "14: b = -c" - }; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/BackEnd/CallTests.cs b/Interpreter.Tests/Unit/BackEnd/CallTests.cs deleted file mode 100644 index 48571a99..00000000 --- a/Interpreter.Tests/Unit/BackEnd/CallTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Interpreter.Lib.BackEnd; -using Xunit; - -namespace Interpreter.Tests.Unit.BackEnd; - -public class CallTests -{ - [Fact] - public void ToStringCorrect() - { - var call = new Call(9, new FunctionInfo("func"), - new List<(string Id, object Value)> - { - ("arg", 1) - } - ); - const string expected = "9 => 0: func(arg: 1)"; - Assert.Equal(expected, call.ToString()); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/BackEnd/FunctionInfoTests.cs b/Interpreter.Tests/Unit/BackEnd/FunctionInfoTests.cs deleted file mode 100644 index f0fc3116..00000000 --- a/Interpreter.Tests/Unit/BackEnd/FunctionInfoTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Interpreter.Lib.BackEnd; -using Xunit; - -namespace Interpreter.Tests.Unit.BackEnd; - -public class FunctionInfoTests -{ - [Theory] - [InlineData("func", null, "func")] - [InlineData("func", "obj", "obj.func")] - public void CallIdCorrectTest(string id, string methodOf, string expected) => - Assert.Equal(expected, new FunctionInfo(id, 0, methodOf).CallId()); -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/BackEnd/InstructionsTests.cs b/Interpreter.Tests/Unit/BackEnd/InstructionsTests.cs deleted file mode 100644 index 6111c5d7..00000000 --- a/Interpreter.Tests/Unit/BackEnd/InstructionsTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Tests.TestData; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.BackEnd; - -public class InstructionsTests -{ - [Theory] - [ClassData(typeof(InstructionsData))] - public void ToStringCorrectTest(Instruction instruction, string expected) => - Assert.Equal(expected, instruction.ToString()); - - [Fact] - public void ComparisonDependsOnAddressTest() - { - var instruction1 = new Mock(1).Object; - var instruction2 = new Mock(2).Object; - - Assert.Equal(1, instruction2.CompareTo(instruction1)); - } - - [Fact] - public void GotoJumpChangedTest() - { - var @goto = new Goto(0, 1); - @goto.SetJump(5); - Assert.Equal(5, @goto.Jump()); - } - - [Fact] - public void ReturnCallersAddedTest() - { - var @return = new Return(7, 19); - @return.AddCaller(@return.FunctionStart - 2); - Assert.NotEmpty(@return); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/BackEnd/VirtualMachineTests.cs b/Interpreter.Tests/Unit/BackEnd/VirtualMachineTests.cs deleted file mode 100644 index 28a8234e..00000000 --- a/Interpreter.Tests/Unit/BackEnd/VirtualMachineTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -#nullable enable -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.BackEnd.Values; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.BackEnd; - -public class VirtualMachineTests -{ - private readonly VirtualMachine _vm; - - public VirtualMachineTests() - { - _vm = new(new(), new(), new(), TextWriter.Null); - } - - [Fact] - public void CorrectPrintToOutTest() - { - var writer = new Mock(); - writer.Setup(x => x.WriteLine(It.IsAny())) - .Verifiable(); - - var vm = new VirtualMachine(new(), new Stack(new[] { new Frame() }), new(), writer.Object); - var print = new Print(0, new Constant(223, "223")); - - print.Execute(vm); - writer.Verify(x => x.WriteLine( - It.Is(v => v!.Equals(223)) - ), Times.Once()); - } - - [Fact] - public void ProgramWithoutHaltWillNotRunTest() - { - var program = new List(); - Assert.Throws(() => _vm.Run(program)); - - program.Add(new Halt(0)); - Assert.Null(Record.Exception(() => _vm.Run(program))); - } - - [Fact] - public void VirtualMachineFramesClearedAfterExecutionTest() - { - var program = new List() - { - new Simple("a", (new Constant(1, "1"), new Constant(2, "2")), "+", 0), - new AsString("b", new Name("a"), 1), - new Halt(2) - }; - - _vm.Run(program); - Assert.Empty(_vm.Frames); - } - - [Fact] - public void VirtualMachineHandlesRecursionTest() - { - var halt = new Mock(12).Trackable(); - var factorial = new FunctionInfo("fact", 1); - var program = new List - { - new Goto(10, 0), - new BeginFunction(1, factorial), - new Simple("_t2", (new Name("n"), new Constant(2, "2")), "<", 2), - new IfNotGoto(new Name("_t2"), 5, 3), - new Return(1, 4, new Name("n")), - new Simple("_t5", (new Name("n"), new Constant(1, "1")), "-", 5), - new PushParameter(6, "n", new Name("_t5")), - new CallFunction(factorial, 7, 1, "f"), - new Simple("_t8", (new Name("n"), new Name("f")), "*", 8), - new Return(1, 9, new Name("_t8")), - new PushParameter(10, "n", new Constant(6, "6")), - new CallFunction(factorial, 11, 1, "fa6"), - halt.Object - }; - - _vm.Run(program); - Assert.Empty(_vm.CallStack); - Assert.Empty(_vm.Arguments); - halt.Verify(x => x.Execute( - It.Is( - vm => Convert.ToInt32(vm.Frames.Peek()["fa6"]) == 720 - ) - ), Times.Once()); - _vm.Frames.Pop(); - } - - [Fact] - public void CreateArrayReservesCertainSpaceTest() - { - var vm = new VirtualMachine(); - vm.Frames.Push(new Frame()); - - var createArray = new CreateArray(0, "arr", 6); - createArray.Execute(vm); - Assert.Equal(6, ((List) vm.Frames.Peek()["arr"]).Count); - - var indexAssignment = new IndexAssignment("arr", (new Constant(0, "0"), new Constant(0, "0")), 1); - indexAssignment.Execute(vm); - Assert.Equal(0, ((List) vm.Frames.Peek()["arr"])[0]); - - var removeFromArray = new RemoveFromArray(2, "arr", new Constant(5, "5")); - removeFromArray.Execute(vm); - Assert.Equal(5, ((List) vm.Frames.Peek()["arr"]).Count); - } - - [Fact] - public void ObjectCreationTest() - { - var halt = new Mock(2).Trackable(); - var program = new List - { - new CreateObject(0, "obj"), - new DotAssignment("obj", (new Constant("prop", "prop"), new Constant(null, "null")), 1), - halt.Object - }; - - _vm.Run(program); - halt.Verify(x => x.Execute( - It.Is( - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - vm => ((Dictionary)vm.Frames.Peek()["obj"])["prop"] == null - ) - ), Times.Once()); - _vm.Frames.Pop(); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/FrontEnd/LexerTests.cs b/Interpreter.Tests/Unit/FrontEnd/LexerTests.cs deleted file mode 100644 index fe187cb8..00000000 --- a/Interpreter.Tests/Unit/FrontEnd/LexerTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Impl; -using Interpreter.Services.Providers.Impl.StructureProvider; -using Interpreter.Tests.TestData; -using Xunit; - -namespace Interpreter.Tests.Unit.FrontEnd; - -public class LexerTests -{ - private readonly Lexer _lexer; - - public LexerTests() - { - _lexer = new Lexer(new StructureProvider().CreateStructure()); - } - - [Theory] - [ClassData(typeof(LexerSuccessData))] - public void LexerDoesNotThrowTest(string text) => - Assert.Null(Record.Exception(() => _lexer.GetTokens(text))); - - [Theory] - [ClassData(typeof(LexerFailData))] - public void LexerThrowsErrorTest(string text) => - Assert.Throws(() => _lexer.GetTokens(text)); - - [Fact] - public void LexerToStringCorrectTest() - { - const string text = "8"; - var tokens = _lexer.GetTokens(text); - Assert.Contains("EOP", _lexer.ToString()); - Assert.Equal("IntegerLiteral (1, 1)-(1, 2): 8", tokens.First().ToString()); - } - - [Fact] - public void EmptyTextTest() => - Assert.NotEmpty(_lexer.GetTokens("")); - - [Fact] - public void GetTokensSkipIgnorableTypesTest() - { - const string text = @" - let x = 1 // int - "; - var tokens = _lexer.GetTokens(text); - Assert.DoesNotContain(_lexer.Structure.FindByTag("Comment"), tokens.Select(x => x.Type)); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/FrontEnd/ParserTests.cs b/Interpreter.Tests/Unit/FrontEnd/ParserTests.cs deleted file mode 100644 index 2628b567..00000000 --- a/Interpreter.Tests/Unit/FrontEnd/ParserTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Impl; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.FrontEnd.TopDownParse.Impl; -using Interpreter.Services.Providers.Impl.StructureProvider; -using Interpreter.Tests.TestData; -using Xunit; - -namespace Interpreter.Tests.Unit.FrontEnd; - -public class ParserTests -{ - private readonly IParser _parser; - - public ParserTests() - { - _parser = new Parser(new Lexer( - new StructureProvider() - .CreateStructure() - )); - } - - [Theory] - [ClassData(typeof(ParserSuccessTestData))] - public void ParserDoesNotThrowTest(string text) - { - var ex = Record.Exception(() => - { - // ReSharper disable once UnusedVariable - var ast = _parser.TopDownParse(text); - }); - Assert.Null(ex); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/FrontEnd/StructureTests.cs b/Interpreter.Tests/Unit/FrontEnd/StructureTests.cs deleted file mode 100644 index 9035b6a5..00000000 --- a/Interpreter.Tests/Unit/FrontEnd/StructureTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; -using Xunit; - -namespace Interpreter.Tests.Unit.FrontEnd; - -public class StructureTests -{ - [Fact] - public void ToStringCorrectTest() - { - var tokenTypes = new List - { - new ("MyToken", "[m|M][y|Y]", 2), - new ("OneToSeven", "[1-7]", 1) - }; - var structure = new Structure(tokenTypes); - - var expectedText = string.Join('\n', - new List - { - "OneToSeven [1-7]", - "MyToken [m|M][y|Y]", - "EOP ", - "ERROR \\S+" - } - ); - Assert.Equal(expectedText,structure.ToString()); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/IR/AstNodeTests.cs b/Interpreter.Tests/Unit/IR/AstNodeTests.cs deleted file mode 100644 index 95344cb0..00000000 --- a/Interpreter.Tests/Unit/IR/AstNodeTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes; -using Interpreter.Lib.IR.Ast.Nodes.Declarations; -using Interpreter.Lib.IR.Ast.Nodes.Statements; -using Interpreter.Lib.IR.CheckSemantics.Types; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.IR; - -public class AstNodeTests -{ - [Fact] - public void PrecedenceTest() - { - var fType = new Mock(new Mock("").Object, new List()); - var funcSymbol = new FunctionSymbol("f", new List(), fType.Object); - - var lexicalDecl = new LexicalDeclaration(false); - var stmtItemList = new List - { - lexicalDecl - }; - // ReSharper disable once UnusedVariable - var func = new FunctionDeclaration(funcSymbol, new BlockStatement(stmtItemList)); - - Assert.True(lexicalDecl.ChildOf()); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/IR/ExpressionTests.cs b/Interpreter.Tests/Unit/IR/ExpressionTests.cs deleted file mode 100644 index 30663b57..00000000 --- a/Interpreter.Tests/Unit/IR/ExpressionTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes.Expressions; -using Interpreter.Lib.IR.Ast.Nodes.Expressions.PrimaryExpressions; -using Xunit; - -namespace Interpreter.Tests.Unit.IR; - -public class ExpressionTests -{ - [Fact] - public void BinaryExpressionTest() - { - var number = new Type("number"); - - var left = new Literal(number, 0); - var right = new Literal(number, 1); - - var binExpr = new BinaryExpression(left, "-", right); - - var ex = Record.Exception(() => binExpr.SemanticCheck()); - Assert.Null(ex); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/IR/SymbolTableTests.cs b/Interpreter.Tests/Unit/IR/SymbolTableTests.cs deleted file mode 100644 index d526c30d..00000000 --- a/Interpreter.Tests/Unit/IR/SymbolTableTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Interpreter.Lib.IR.Ast.Nodes; -using Interpreter.Lib.IR.CheckSemantics.Variables; -using Interpreter.Lib.IR.CheckSemantics.Variables.Symbols; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.IR; - -public class SymbolTableTests -{ - [Fact] - public void FindSymbolTest() - { - const string id = "ident"; - var type = new Mock(id); - - var symbol = new Mock(id, type.Object); - symbol.Setup(s => s.Id).Returns(id); - symbol.Setup(s => s.Type).Returns(type.Object); - - var outerScope = new SymbolTable(); - var innerScope = new SymbolTable(); - outerScope.AddSymbol(symbol.Object); - innerScope.AddOpenScope(outerScope); - - Assert.NotNull(innerScope.FindSymbol(id)); - Assert.True(outerScope.ContainsSymbol(id)); - } - - [Fact] - public void FlatteningScopeTest() - { - var table = new SymbolTable(); - var stmtList = new List( - Enumerable.Repeat( - new Mock().Object, - new Random().Next(10) - ) - ); - var script = new ScriptBody(stmtList) - { - SymbolTable = table - }; - script.ToList().ForEach(node => node.SymbolTable = table); - - const string id = "ident"; - var type = new Mock(id); - - var symbol = new Mock(id, type.Object); - symbol.Setup(s => s.Id).Returns(id); - symbol.Setup(s => s.Type).Returns(type.Object); - - script.SymbolTable.AddSymbol(symbol.Object); - - Assert.All( - script.ToList(), - stmtListItem => - Assert.True(stmtListItem.SymbolTable.ContainsSymbol(id)) - ); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/Infrastructure/ExecutorTests.cs b/Interpreter.Tests/Unit/Infrastructure/ExecutorTests.cs deleted file mode 100644 index 3c0d04f3..00000000 --- a/Interpreter.Tests/Unit/Infrastructure/ExecutorTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.IR.Ast; -using Interpreter.Services.Executor.Impl; -using Interpreter.Services.Parsing; -using Interpreter.Tests.Stubs; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.Infrastructure; - -public class ExecutorTests -{ - private readonly Mock _settings; - private readonly Mock _parsingService; - - public ExecutorTests() - { - _settings = new Mock(); - _settings.Setup(x => x.Dump).Returns(false); - _settings.Setup(x => x.InputFilePath).Returns("file.js"); - - _parsingService = new Mock(); - } - - [Fact] - public void ExecuteGoesOkTest() - { - var ast = new Mock(); - ast.Setup(x => x.GetInstructions()) - .Returns(new List { new Halt(0) }); - - _parsingService.Setup(x => x.Parse(It.IsAny())) - .Returns(ast.Object); - - var executor = new Executor(_parsingService.Object, _settings.ToOptions()); - Assert.Null(Record.Exception(() => executor.Execute())); - } - - [Fact] - public void SemanticExceptionCaughtTest() - { - var ast = new Mock(); - ast.Setup(x => x.GetInstructions()) - .Throws(); - - _parsingService.Setup(x => x.Parse(It.IsAny())) - .Returns(ast.Object); - - var executor = new Executor(_parsingService.Object, _settings.ToOptions()); - Assert.Null(Record.Exception(() => executor.Execute())); - } - - [Fact] - public void LexerExceptionCaughtTest() - { - _parsingService.Setup(x => x.Parse(It.IsAny())) - .Throws(); - - var executor = new Executor(_parsingService.Object, _settings.ToOptions()); - Assert.Null(Record.Exception(() => executor.Execute())); - } - - [Fact] - public void ParserExceptionCaughtTest() - { - _parsingService.Setup(x => x.Parse(It.IsAny())) - .Throws(); - - var executor = new Executor(_parsingService.Object, _settings.ToOptions()); - Assert.Null(Record.Exception(() => executor.Execute())); - } - - [Fact] - public void InternalInterpreterErrorCaughtTest() - { - var instruction = new Mock(MockBehavior.Default, 0); - instruction.Setup(x => x.Execute(It.IsAny())) - .Throws(); - - var ast = new Mock(); - ast.Setup(x => x.GetInstructions()) - .Returns(new List { instruction.Object, new Halt(1) }); - - _parsingService.Setup(x => x.Parse(It.IsAny())) - .Returns(ast.Object); - - var executor = new Executor(_parsingService.Object, _settings.ToOptions()); - Assert.Null(Record.Exception(() => executor.Execute())); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs b/Interpreter.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs deleted file mode 100644 index 53e63e33..00000000 --- a/Interpreter.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.IO.Abstractions; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.IR.Ast; -using Interpreter.Services.Providers.Impl.LexerProvider; -using Interpreter.Services.Providers.Impl.ParserProvider; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.Infrastructure; - -public class LoggingEntitiesTests -{ - private readonly Mock _file; - private readonly Mock _fileSystem; - - public LoggingEntitiesTests() - { - _file = new Mock(); - - _fileSystem = new Mock(); - _fileSystem.Setup(x => x.File) - .Returns(_file.Object); - } - - [Fact] - public void CorrectFileNameProducedByLexerTest() - { - var lexer = new Mock(); - lexer.Setup(x => x.GetTokens(It.IsAny())) - .Returns(new List()); - lexer.Setup(x => x.ToString()) - .Returns("lexer"); - - _file.Setup(x => x.WriteAllText( - It.IsAny(), It.IsAny() - )).Verifiable(); - - var loggingLexer = new LoggingLexer(lexer.Object, "file", _fileSystem.Object); - loggingLexer.GetTokens(""); - - _file.Verify(x => x.WriteAllText( - It.Is(p => p == "file.tokens"), It.Is(c => c == "lexer") - ), Times.Once()); - } - - [Fact] - public void CorrectTreeWrittenAndLoggingTreeProducedTest() - { - var ast = new Mock(); - ast.Setup(x => x.ToString()) - .Returns("digraph ast { }"); - - var parser = new Mock(); - parser.Setup(x => x.TopDownParse(It.IsAny())) - .Returns(ast.Object); - - _file.Setup(x => x.WriteAllText( - It.IsAny(), It.IsAny() - )).Verifiable(); - - var loggingParser = new LoggingParser(parser.Object, "file", _fileSystem.Object); - var parsed = loggingParser.TopDownParse(""); - - _file.Verify(x => x.WriteAllText( - It.Is(p => p == "ast.dot"), - It.Is(c => c == "digraph ast { }") - ), Times.Once()); - Assert.IsType(parsed); - } - - [Fact] - public void CorrectFileNameProducedByTreeTest() - { - var ast = new Mock(); - ast.Setup(x => x.GetInstructions()) - .Returns(new List { new Halt(0) }); - - _file.Setup(x => x.WriteAllLines( - It.IsAny(), It.IsAny>() - )).Verifiable(); - - var loggingTree = new LoggingAbstractSyntaxTree(ast.Object, "file", _fileSystem.Object); - loggingTree.GetInstructions(); - - _file.Verify(x => x.WriteAllLines( - It.Is(p => p == "file.tac"), - It.Is>(c => c.SequenceEqual(new[] { "0: End" })) - ), Times.Once()); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/Infrastructure/ParsingServiceTests.cs b/Interpreter.Tests/Unit/Infrastructure/ParsingServiceTests.cs deleted file mode 100644 index d9ee4fd6..00000000 --- a/Interpreter.Tests/Unit/Infrastructure/ParsingServiceTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.IR.Ast; -using Interpreter.Services.Parsing.Impl; -using Interpreter.Services.Providers; -using Moq; -using Xunit; - -namespace Interpreter.Tests.Unit.Infrastructure; - -public class ParsingServiceTests -{ - [Fact] - public void CertainTextHasBeenParsedTest() - { - const string text = "let x = 1 + 2 - 3"; - - var ast = new Mock(); - var parser = new Mock(); - parser.Setup(x => x.TopDownParse(It.IsAny())) - .Returns(ast.Object).Verifiable(); - - var parserProvider = new Mock(); - parserProvider.Setup(x => x.CreateParser()) - .Returns(parser.Object); - - var parsingService = new ParsingService(parserProvider.Object); - parsingService.Parse(text); - - parser.Verify(x => x.TopDownParse( - It.Is(s => s == text) - ), Times.Once()); - } -} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/Infrastructure/ProvidersTests.cs b/Interpreter.Tests/Unit/Infrastructure/ProvidersTests.cs deleted file mode 100644 index 1d7a103a..00000000 --- a/Interpreter.Tests/Unit/Infrastructure/ProvidersTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; -using Interpreter.Lib.FrontEnd.GetTokens.Impl; -using Interpreter.Lib.FrontEnd.TopDownParse.Impl; -using Interpreter.Services.Providers; -using Interpreter.Services.Providers.Impl.LexerProvider; -using Interpreter.Services.Providers.Impl.ParserProvider; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; -using SystemType = System.Type; - -namespace Interpreter.Tests.Unit.Infrastructure; - -public class ProvidersTests -{ - [Theory] - [InlineData(typeof(Lexer), false)] - [InlineData(typeof(LoggingLexer), true)] - public void CertainLexerProvidedTest(SystemType lexerType, bool dump) - { - var structureProvider = new Mock(); - structureProvider.Setup(x => x.CreateStructure()) - .Returns(new Structure(new List())); - - var options = new Mock>(); - options.Setup(x => x.Value) - .Returns(new CommandLineSettings - { - Dump = dump, - InputFilePath = "file.js" - }); - - var lexerProvider = new LexerProvider(structureProvider.Object, options.Object); - var lexer = lexerProvider.CreateLexer(); - - Assert.IsType(lexerType, lexer); - } - - [Theory] - [InlineData(typeof(Parser), false)] - [InlineData(typeof(LoggingParser), true)] - public void CertainParserProvidedTest(SystemType parserType, bool dump) - { - var options = new Mock>(); - options.Setup(x => x.Value) - .Returns(new CommandLineSettings - { - Dump = dump, - InputFilePath = "file.js" - }); - - var lexer = new Mock(); - var lexerProvider = new Mock(); - lexerProvider.Setup(x => x.CreateLexer()) - .Returns(lexer.Object); - - var parserProvider = new ParserProvider(lexerProvider.Object, options.Object); - var parser = parserProvider.CreateParser(); - - Assert.IsType(parserType, parser); - } -} \ No newline at end of file diff --git a/Interpreter/CommandLineSettings.cs b/Interpreter/CommandLineSettings.cs deleted file mode 100644 index a56c9ea6..00000000 --- a/Interpreter/CommandLineSettings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using CommandLine; -using CommandLine.Text; - -namespace Interpreter; - -[SuppressMessage("ReSharper", "UnusedMember.Global")] -[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] -[ExcludeFromCodeCoverage] -// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global -public class CommandLineSettings -{ - [Value(0, MetaName = "InputFilePath", Required = true, HelpText = "Path to input file")] - public virtual string InputFilePath { get; set; } - - [Option('d', "dump", Default = false, HelpText = "Show dump data of interpreter")] - public virtual bool Dump { get; set; } - - [Usage(ApplicationAlias = "Interpreter")] - public static IEnumerable Examples - { - get - { - yield return new Example("Simple interpretation call", - new CommandLineSettings { InputFilePath = "file.js" }); - yield return new Example("Request dump", - new CommandLineSettings { InputFilePath = "file.js", Dump = true }); - } - } - - public string GetInputFileName() => - InputFilePath.Split(".js")[0]; - - public virtual string GetText() => - File.ReadAllText(InputFilePath); -} \ No newline at end of file diff --git a/Interpreter/Interpreter.csproj b/Interpreter/Interpreter.csproj deleted file mode 100644 index c6708e46..00000000 --- a/Interpreter/Interpreter.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net7.0 - 1.2.6 - enable - - - - - - - - - - - - - - diff --git a/Interpreter/Program.cs b/Interpreter/Program.cs deleted file mode 100644 index 4c450e67..00000000 --- a/Interpreter/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using CommandLine; -using Microsoft.Extensions.DependencyInjection; -using Interpreter.Services.Executor; -using Interpreter.Services.Executor.Impl; -using Interpreter.Services.Parsing; -using Interpreter.Services.Parsing.Impl; -using Interpreter.Services.Providers; -using Interpreter.Services.Providers.Impl.LexerProvider; -using Interpreter.Services.Providers.Impl.ParserProvider; -using Interpreter.Services.Providers.Impl.StructureProvider; -using Microsoft.Extensions.Options; - -namespace Interpreter; - -[ExcludeFromCodeCoverage] -public static class Program -{ - private static IServiceCollection ServiceCollection { get; } = new ServiceCollection(); - private static IServiceProvider ServiceProvider { get; set; } - - private static void Main(string[] args) => - Parser.Default.ParseArguments(args) - .WithParsed(options => - { - ConfigureServices(options); - ServiceProvider - .GetService()! - .Execute(); - }) - .WithNotParsed(errors => errors.Output()); - - - private static void ConfigureServices(CommandLineSettings settings) - { - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - - ServiceCollection.AddSingleton(); - - ServiceCollection.AddSingleton(_ => Options.Create(settings)); - - ServiceProvider = ServiceCollection.BuildServiceProvider(); - } -} \ No newline at end of file diff --git a/Interpreter/Services/Executor/IExecutor.cs b/Interpreter/Services/Executor/IExecutor.cs deleted file mode 100644 index f2f22248..00000000 --- a/Interpreter/Services/Executor/IExecutor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Interpreter.Services.Executor; - -public interface IExecutor -{ - void Execute(); -} \ No newline at end of file diff --git a/Interpreter/Services/Executor/Impl/Executor.cs b/Interpreter/Services/Executor/Impl/Executor.cs deleted file mode 100644 index 9b12c337..00000000 --- a/Interpreter/Services/Executor/Impl/Executor.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Interpreter.Lib.BackEnd; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.IR.CheckSemantics.Exceptions; -using Interpreter.Services.Parsing; -using Microsoft.Extensions.Options; - -namespace Interpreter.Services.Executor.Impl; - -public class Executor : IExecutor -{ - private readonly IParsingService _parsingService; - private readonly CommandLineSettings _commandLineSettings; - - public Executor(IParsingService parsingService, IOptions options) - { - _parsingService = parsingService; - _commandLineSettings = options.Value; - } - - public void Execute() - { - try - { - var ast = _parsingService.Parse(_commandLineSettings.GetText()); - var instructions = ast.GetInstructions(); - - var vm = new VirtualMachine(); - vm.Run(instructions); - } - catch (Exception ex) - when (ex is LexerException or ParserException or SemanticException) - { - Console.WriteLine(ex.Message); - } - catch (Exception ex) - { - Console.WriteLine("Internal Interpreter Error"); - Console.WriteLine(ex); - } - } -} \ No newline at end of file diff --git a/Interpreter/Services/Parsing/IParsingService.cs b/Interpreter/Services/Parsing/IParsingService.cs deleted file mode 100644 index 7b9249e2..00000000 --- a/Interpreter/Services/Parsing/IParsingService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.IR.Ast; - -namespace Interpreter.Services.Parsing; - -public interface IParsingService -{ - IAbstractSyntaxTree Parse(string text); -} \ No newline at end of file diff --git a/Interpreter/Services/Parsing/Impl/ParsingService.cs b/Interpreter/Services/Parsing/Impl/ParsingService.cs deleted file mode 100644 index 2c24a572..00000000 --- a/Interpreter/Services/Parsing/Impl/ParsingService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Interpreter.Lib.IR.Ast; -using Interpreter.Services.Providers; - -namespace Interpreter.Services.Parsing.Impl; - -public class ParsingService : IParsingService -{ - private readonly IParserProvider _parserProvider; - - public ParsingService(IParserProvider parserProvider) - { - _parserProvider = parserProvider; - } - - public IAbstractSyntaxTree Parse(string text) - { - var parser = _parserProvider.CreateParser(); - return parser.TopDownParse(text); - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/ILexerProvider.cs b/Interpreter/Services/Providers/ILexerProvider.cs deleted file mode 100644 index 0f34a033..00000000 --- a/Interpreter/Services/Providers/ILexerProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens; - -namespace Interpreter.Services.Providers; - -public interface ILexerProvider -{ - ILexer CreateLexer(); -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/IParserProvider.cs b/Interpreter/Services/Providers/IParserProvider.cs deleted file mode 100644 index cd7b7101..00000000 --- a/Interpreter/Services/Providers/IParserProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.FrontEnd.TopDownParse; - -namespace Interpreter.Services.Providers; - -public interface IParserProvider -{ - IParser CreateParser(); -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/IStructureProvider.cs b/Interpreter/Services/Providers/IStructureProvider.cs deleted file mode 100644 index 037c97e6..00000000 --- a/Interpreter/Services/Providers/IStructureProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Services.Providers; - -public interface IStructureProvider -{ - Structure CreateStructure(); -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/LexerProvider/LexerProvider.cs b/Interpreter/Services/Providers/Impl/LexerProvider/LexerProvider.cs deleted file mode 100644 index 4ff77b32..00000000 --- a/Interpreter/Services/Providers/Impl/LexerProvider/LexerProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO.Abstractions; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Impl; -using Microsoft.Extensions.Options; - -namespace Interpreter.Services.Providers.Impl.LexerProvider; - -public class LexerProvider : ILexerProvider -{ - private readonly IStructureProvider _structureProvider; - private readonly CommandLineSettings _settings; - - public LexerProvider(IStructureProvider structureProvider, IOptions options) - { - _structureProvider = structureProvider; - _settings = options.Value; - } - - public ILexer CreateLexer() - { - var structure = _structureProvider.CreateStructure(); - var lexer = new Lexer(structure); - return _settings.Dump - ? new LoggingLexer(lexer, _settings.GetInputFileName(), new FileSystem()) - : lexer; - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/LexerProvider/LoggingLexer.cs b/Interpreter/Services/Providers/Impl/LexerProvider/LoggingLexer.cs deleted file mode 100644 index ed297b16..00000000 --- a/Interpreter/Services/Providers/Impl/LexerProvider/LoggingLexer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.IO.Abstractions; -using Interpreter.Lib.FrontEnd.GetTokens; -using Interpreter.Lib.FrontEnd.GetTokens.Data; - -namespace Interpreter.Services.Providers.Impl.LexerProvider; - -public class LoggingLexer : ILexer -{ - private readonly ILexer _lexer; - private readonly string _fileName; - private readonly IFileSystem _fileSystem; - - public LoggingLexer(ILexer lexer, string fileName, IFileSystem fileSystem) - { - _lexer = lexer; - _fileName = fileName; - _fileSystem = fileSystem; - } - - [ExcludeFromCodeCoverage] - public Structure Structure => _lexer.Structure; - - public List GetTokens(string text) - { - var tokens = _lexer.GetTokens(text); - _fileSystem.File.WriteAllText( - $"{_fileName}.tokens", - _lexer.ToString() - ); - return tokens; - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/ParserProvider/LoggingAbstractSyntaxTree.cs b/Interpreter/Services/Providers/Impl/ParserProvider/LoggingAbstractSyntaxTree.cs deleted file mode 100644 index b8160dbc..00000000 --- a/Interpreter/Services/Providers/Impl/ParserProvider/LoggingAbstractSyntaxTree.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.IO.Abstractions; -using Interpreter.Lib.BackEnd.Instructions; -using Interpreter.Lib.IR.Ast; - -namespace Interpreter.Services.Providers.Impl.ParserProvider; - -public class LoggingAbstractSyntaxTree : IAbstractSyntaxTree -{ - private readonly IAbstractSyntaxTree _ast; - private readonly string _fileName; - private readonly IFileSystem _fileSystem; - - public LoggingAbstractSyntaxTree(IAbstractSyntaxTree ast, string fileName, IFileSystem fileSystem) - { - _ast = ast; - _fileName = fileName; - _fileSystem = fileSystem; - } - - public List GetInstructions() - { - var instructions = _ast.GetInstructions(); - _fileSystem.File.WriteAllLines( - $"{_fileName}.tac", - instructions.OrderBy(i => i).Select(i => i.ToString()) - ); - return instructions; - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/ParserProvider/LoggingParser.cs b/Interpreter/Services/Providers/Impl/ParserProvider/LoggingParser.cs deleted file mode 100644 index 3fd04d97..00000000 --- a/Interpreter/Services/Providers/Impl/ParserProvider/LoggingParser.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO.Abstractions; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Interpreter.Lib.IR.Ast; - -namespace Interpreter.Services.Providers.Impl.ParserProvider; - -public class LoggingParser : IParser -{ - private readonly IParser _parser; - private readonly string _fileName; - private readonly IFileSystem _fileSystem; - - public LoggingParser(IParser parser, string fileName, IFileSystem fileSystem) - { - _parser = parser; - _fileName = fileName; - _fileSystem = fileSystem; - } - - public IAbstractSyntaxTree TopDownParse(string text) - { - var ast = _parser.TopDownParse(text); - var astDot = ast.ToString(); - _fileSystem.File.WriteAllText("ast.dot", astDot); - return new LoggingAbstractSyntaxTree(ast, _fileName, _fileSystem); - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/ParserProvider/ParserProvider.cs b/Interpreter/Services/Providers/Impl/ParserProvider/ParserProvider.cs deleted file mode 100644 index 25b5b3a2..00000000 --- a/Interpreter/Services/Providers/Impl/ParserProvider/ParserProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO.Abstractions; -using Interpreter.Lib.FrontEnd.TopDownParse; -using Parser = Interpreter.Lib.FrontEnd.TopDownParse.Impl.Parser; -using Microsoft.Extensions.Options; - -namespace Interpreter.Services.Providers.Impl.ParserProvider; - -public class ParserProvider : IParserProvider -{ - private readonly ILexerProvider _lexerProvider; - private readonly CommandLineSettings _settings; - - public ParserProvider(ILexerProvider lexerProvider, IOptions options) - { - _lexerProvider = lexerProvider; - _settings = options.Value; - } - - public IParser CreateParser() - { - var lexer = _lexerProvider.CreateLexer(); - var parser = new Parser(lexer); - return _settings.Dump - ? new LoggingParser(parser, _settings.GetInputFileName(), new FileSystem()) - : parser; - } -} \ No newline at end of file diff --git a/Interpreter/Services/Providers/Impl/StructureProvider/StructureProvider.cs b/Interpreter/Services/Providers/Impl/StructureProvider/StructureProvider.cs deleted file mode 100644 index 90f5e186..00000000 --- a/Interpreter/Services/Providers/Impl/StructureProvider/StructureProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Serialization; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; - -namespace Interpreter.Services.Providers.Impl.StructureProvider; - -public class StructureProvider : IStructureProvider -{ - public Structure CreateStructure() => - JsonSerializer.Deserialize( - TokenTypes.Json, - new JsonSerializerOptions - { - Converters = { new StructureReadConverter() } - }); - - [ExcludeFromCodeCoverage] - private class StructureReadConverter : JsonConverter - { - public override Structure Read(ref Utf8JsonReader reader, - Type typeToConvert, JsonSerializerOptions options) - { - using var jsonDocument = JsonDocument.ParseValue(ref reader); - var tokenTypes = jsonDocument.RootElement - .EnumerateArray().Select(element => - { - var tag = element.GetProperty("tag").GetString(); - var pattern = element.GetProperty("pattern").GetString(); - var priority = element.GetProperty("priority").GetInt32(); - - var ignorable = element.TryGetProperty("canIgnore", out var canIgnore); - - return ignorable && canIgnore.GetBoolean() - ? new IgnorableType(tag, pattern, priority) - : new TokenType(tag, pattern, priority); - }).ToList(); - return new Structure(tokenTypes); - } - - public override void Write(Utf8JsonWriter writer, - Structure value, JsonSerializerOptions options) => - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/Interpreter/TokenTypes.cs b/Interpreter/TokenTypes.cs deleted file mode 100644 index f604bdba..00000000 --- a/Interpreter/TokenTypes.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace Interpreter; - -public static class TokenTypes -{ - public const string Json = - """ - [ - { - "tag": "Comment", - "pattern": "[\/]{2}.*", - "priority": 0, - "canIgnore": true - }, - { - "tag": "Ident", - "pattern": "[a-zA-Z][a-zA-Z0-9]*", - "priority": 50 - }, - { - "tag": "IntegerLiteral", - "pattern": "[0-9]+", - "priority": 3 - }, - { - "tag": "FloatLiteral", - "pattern": "[0-9]+[.][0-9]+", - "priority": 2 - }, - { - "tag": "NullLiteral", - "pattern": "null", - "priority": 4 - }, - { - "tag": "BooleanLiteral", - "pattern": "true|false", - "priority": 5 - }, - { - "tag": "StringLiteral", - "pattern": "\\\"(\\\\.|[^\"\\\\])*\\\"", - "priority": 6 - }, - { - "tag": "Keyword", - "pattern": "let|const|function|if|else|while|break|continue|return|as|type", - "priority": 11 - }, - { - "tag": "Operator", - "pattern": "[+]{1,2}|[-]|[*]|[\/]|[%]|([!]|[=])[=]|([<]|[>])[=]?|[!]|[|]{2}|[&]{2}|[~]|[:]{2}", - "priority": 12 - }, - { - "tag": "Arrow", - "pattern": "[=][>]", - "priority": 13 - }, - { - "tag": "Comma", - "pattern": "[,]", - "priority": 100 - }, - { - "tag": "Dot", - "pattern": "[.]", - "priority": 105 - }, - { - "tag": "LeftCurl", - "pattern": "[{]", - "priority": 101 - }, - { - "tag": "RightCurl", - "pattern": "[}]", - "priority": 102 - }, - { - "tag": "LeftParen", - "pattern": "[(]", - "priority": 103 - }, - { - "tag": "RightParen", - "pattern": "[)]", - "priority": 104 - }, - { - "tag": "LeftBracket", - "pattern": "[[]", - "priority": 107 - }, - { - "tag": "RightBracket", - "pattern": "[]]", - "priority": 109 - }, - { - "tag": "Assign", - "pattern": "[=]", - "priority": 99 - }, - { - "tag": "QuestionMark", - "pattern": "[?]", - "priority": 90 - }, - { - "tag": "Colon", - "pattern": "[:]", - "priority": 91 - }, - { - "tag": "SemiColon", - "pattern": "[;]", - "priority": 92 - } - ] - """; -} \ No newline at end of file diff --git a/Readme.md b/Readme.md index de8ec1ab..5c67160b 100644 --- a/Readme.md +++ b/Readme.md @@ -1,10 +1,18 @@ -![Code Coverage](https://img.shields.io/badge/Code%20Coverage-43%25-critical?style=flat) +# HydraScript -Package | Line Rate | Health --------- | --------- | ------ -Interpreter.Lib | 40% | ❌ -Interpreter | 100% | ✔ -**Summary** | **43%** (925 / 2173) | ❌ +![logo](hydrascript-logo.jpg) + +![Code Coverage](https://img.shields.io/badge/Code%20Coverage-47%25-critical?style=flat) + +| Package | Line Rate | Health | +|----------------------------------------|-----------------------|--------| +| HydraScript.Domain.BackEnd | 81% | ➖ | +| HydraScript.Infrastructure | 70% | ❌ | +| HydraScript.Domain.FrontEnd | 57% | ❌ | +| HydraScript.Domain.IR | 74% | ❌ | +| HydraScript.Application.CodeGeneration | 0% | ❌ | +| HydraScript.Application.StaticAnalysis | 4% | ❌ | +| **Summary** | **47%** (1325 / 2792) | ❌ | _Minimum allowed line rate is `80%`_ @@ -14,14 +22,16 @@ _Minimum allowed line rate is `80%`_ За основу был взят стандарт [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) -[Лексическая структура](Interpreter/TokenTypes.cs) +[Лексическая структура](src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypesJson.cs) -[Грамматика](Interpreter/grammar.txt) +[Грамматика](src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt) [Рабочие примеры](samples) -### Цель проекта -Реализовать JavaScript с объектами и статической структурной типизацией, избавившись от таких понятий, как `constructor`, `class`, `interface` +### Цели проекта +1. Частично реализовать JavaScript с объектами и статической структурной типизацией, избавившись от таких понятий, как `constructor`, `class`, `interface` +2. Публично реверс-инжинирить современный статический анализ (вывод типов, форвард рефы, ошибки выполнения на стадии компиляции) +3. Упростить понимание области конструирования компиляторов за счёт исходного кода проекта - собрать понятные реализации алгоритмов и типовых задач в репозитории (Lexer, Parser, CFG, SSA, DCE, etc.) ### Конструкции языка @@ -39,17 +49,16 @@ _Minimum allowed line rate is `80%`_ - NullableType (тип, который допускает значение ```null```) - ObjectType (тип объекта, является NullableType) - ArrayType (списковый тип) -- FunctionType (тип функции) ##### Значения по умолчанию -| Тип | Значение | -| ----------- | ----------- | -| number | 0 | -| boolean | false | -|string| ""| -|NullableType|null| -|ArrayType|[]| +| Тип | Значение | +|--------------|----------| +| number | 0 | +| boolean | false | +| string | "" | +| NullableType | null | +| ArrayType | [] | ##### type alias Можно создать свой type alias по типу того, как это сделано в С++ @@ -61,10 +70,9 @@ type point = { x: int; y: int; } -type handleInts = (ints) => void -type handler = { - items: ints; - handle: handleInts; +type composite = { + p: point; + arr: ints; } ``` #### Объявление переменных @@ -73,18 +81,13 @@ let i = 1 // интерпретатор выведет тип из выраже let j: number // запишет значение по умолчанию в переменную let k: number = 1 // полностью явное объявление ``` +#### Функции + #### Объекты ``` let v2d = { - // обычное поле x: 3; y: 4; - //метод - lengthSquared => () { - // в методе доступны поля объекта - // и указатель this - return x * x + y * y - }; } ``` #### Списки @@ -95,18 +98,18 @@ array::1 // удаление элемента по индексу array = array ++ [5, 7] // конкатенация списков ``` #### Операторы -|Оператор|Вид|Типы операндов|Тип операции| -|---|---|---|---| -|+|бинарный|оба number, оба string|number, string -|*, -, /, %| бинарный|number|number -|||, && |бинарный|boolean|boolean -|!=, ==|бинарный|равный с двух сторон|boolean -|<=, >=, >, <|бинарный|number|boolean -|!|унарный|boolean|boolean -|-|унарный|number|number -|++|бинарный|[]|[] -|::|бинарный|[] и number|void -|~|унарный|[]|number +| Оператор | Вид | Типы операндов | Тип операции | +|------------------|----------|------------------------|----------------| +| + | бинарный | оба number, оба string | number, string | +| *, -, /, % | бинарный | number | number | +| ||, && | бинарный | boolean | boolean | +| !=, == | бинарный | равный с двух сторон | boolean | +| <=, >=, >, < | бинарный | number | boolean | +| ! | унарный | boolean | boolean | +| - | унарный | number | number | +| ++ | бинарный | [] | [] | +| :: | бинарный | [] и number | void | +| ~ | унарный | [] | number | #### Ветвление ``` @@ -144,6 +147,29 @@ function add(a: number, b: number): number { // вызов let c = add(1, 2) ``` +#### Методы +``` +// сделаны подобно Go - привязка по имени типа + +// шаг 1. Объявить type alias +type Point2 = { + x: number; + y: number; +} + +// шаг 2. Объявить переменную этого типа +let v2d: Point2 = { + x: 3; + y: 4; +} + +// шаг 3. Указать первым параметром функции - объект типа +function lengthSquared(obj: Point2) { + let x = obj.x + let y = obj.y + return x * x + y * y +} +``` #### Операции доступа ``` // объекты @@ -161,38 +187,34 @@ let s = v2d as string ### Требования -- .NET 7 SDK +- .NET 8 SDK ### Сборка -После клонирования репозитория идём в папку проекта `Interpreter`. +После клонирования репозитория идём в папку проекта `HydraScript`. Там выполняем команду: -```dotnet publish -r -p:PublishSingleFile=true -p:DebugType=embedded --self-contained false -o ``` +```dotnet publish ./src/HydraScript/HydraScript.csproj -r -p:PublishSingleFile=true -p:DebugType=embedded --self-contained false -o ``` Список идентификаторов рантайма лежит [тут](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#windows-rids) ### Запуск +Простой: +``` +HydraScript file.js ``` -Interpreter 1.2.6 -Copyright (C) 2022 Interpreter -USAGE: -Simple interpretation call: - Interpreter file.js -Request dump: - Interpreter --dump file.js - - -d, --dump (Default: false) Show dump data of interpreter - - --help Display this help screen. - - --version Display version information. - InputFilePath (pos. 0) Required. Path to input file +С выводом дебаг инфы (токены, ast, инструкции): +``` +HydraScript file.js --dump ``` ### Источники: -1. [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) -2. [DragonBook](https://suif.stanford.edu/dragonbook/) -3. [Stanford CS143 Lectures](https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/) +1. Курсы "Конструирование Компиляторов" и "Генерация Оптимального Кода" кафедры ИУ-9 МГТУ им. Н.Э. Баумана +2. [ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) +3. [DragonBook](https://suif.stanford.edu/dragonbook/) +4. [Stanford CS143 Lectures](https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/) +5. [Simple Virtual Machine](https://github.com/parrt/simple-virtual-machine) +6. Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции +7. Свердлов С.З. Языки программирования и методы трансляции \ No newline at end of file diff --git a/contributor-licence-agreement.md b/contributor-licence-agreement.md deleted file mode 100644 index 5297004e..00000000 --- a/contributor-licence-agreement.md +++ /dev/null @@ -1,31 +0,0 @@ -# HydraScript Individual Contributor License Agreement - -### *Adapted from http://www.apache.org/licenses/ © Apache Software Foundation.* - -In order to clarify the intellectual property license granted with Contributions from any person or entity, this project (HydraScript) must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms below. This CLA is for your protection as a Contributor as well as the protection of HydraScript and its users; it does not change your rights to use your own Contributions for any other purpose. - -If you have not already done so, you will be requested to sign the CLA within the pull request by copy and pasting **"I have read the CLA Document and I hereby sign the CLA"** as a Pull Request comment. - -Please read this document carefully before signing and keep a copy for your records. - -You accept and agree to the following terms and conditions for your present and future Contributions Submitted to HydraScript. In return, HydraScript shall not use your Contributions in a way that is contrary to the public benefit or inconsistent with its nonprofit status and bylaws in effect at the time of the Contribution. Except for the license granted herein to HydraScript and recipients of software distributed by HydraScript, You reserve all right, title, and interest in and to your Contributions. - -1. Definitions. "You" (or "Contributor") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this CLA with HydraScript. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. - - For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally Submitted by You to HydraScript for inclusion in, or documentation of, any of the products owned or managed by HydraScript (the "Work"). For the purposes of this definition, "Submitted" means any form of electronic, verbal, or written communication sent to HydraScript or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, HydraScript for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." - -2. Grant of Copyright License. Subject to the terms and conditions of this CLA, You hereby grant to HydraScript and to recipients of software distributed by HydraScript a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of this CLA, You hereby grant to HydraScript and to recipients of software distributed by HydraScript a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by your Contribution(s) alone or by combination of your Contribution(s) with the Work to which such Contribution(s) was Submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this CLA for that Contribution or Work shall terminate as of the date such litigation is filed. - -4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to HydraScript, or that your employer has executed a separate Corporate CLA with HydraScript. - -5. You represent that each of your Contributions is your original creation (see section 7 for submissions on behalf of others). You represent that your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of your Contributions. - -6. You are not expected to provide support for your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. - -7. You commit not to copy code from another project which license does not allow the duplication / reuse / modification of their source code and / or license is not compatible with the project you are contributing to. As a reminder, a project without an explicit license must be considered as a project with a copyrighted license. - -8. You agree to notify HydraScript of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. \ No newline at end of file diff --git a/hydrascript-logo.jpg b/hydrascript-logo.jpg new file mode 100644 index 00000000..9bec26da Binary files /dev/null and b/hydrascript-logo.jpg differ diff --git a/samples/cycled.js b/samples/cycled.js new file mode 100644 index 00000000..18ca8e85 --- /dev/null +++ b/samples/cycled.js @@ -0,0 +1,8 @@ +type CycledType = { + x: CycledType; +} +let obj: CycledType = { + x: null; +} +obj.x = obj +print(obj as string) \ No newline at end of file diff --git a/samples/equals.js b/samples/equals.js index c512345e..09724ff7 100644 --- a/samples/equals.js +++ b/samples/equals.js @@ -1,20 +1,17 @@ type withEquals = { prop: number; - equals: (withEquals) => boolean; } -let obj1 = { +function equals(obj: withEquals, that: withEquals) { + return obj.prop == that.prop +} + +let obj1: withEquals = { prop: 1; - equals => (that: withEquals): boolean { - return prop == (that.prop) - }; } -let obj2 = { +let obj2: withEquals = { prop: 2; - equals => (that: withEquals): boolean { - return prop == (that.prop) - }; } let res = obj1.equals(obj2) diff --git a/samples/forwardref.js b/samples/forwardref.js new file mode 100644 index 00000000..5c73092e --- /dev/null +++ b/samples/forwardref.js @@ -0,0 +1,15 @@ +type A = { + b: B; +} + +type B = { + a: A; +} + +let a: A = { + b: { + a: null; + }; +} + +print(a as string) \ No newline at end of file diff --git a/samples/linkedlist.js b/samples/linkedlist.js index 439ff2bf..f3aa7f2f 100644 --- a/samples/linkedlist.js +++ b/samples/linkedlist.js @@ -4,8 +4,27 @@ type node = { } type list = { head: node; - append: (number,) => void; - getOdd: () => number[]; +} + +function append(lst: list, item: number) { + let tail: node = lst.head + while (tail.next != null) { + tail = tail.next + } + tail.next = makeNode(item) +} + +function getOdd(lst: list): number[] { + let result: number[] + let n = lst.head + while (n != null) { + if (n.data % 2 != 0) { + let i = n.data + result = result ++ [i] + } + n = n.next + } + return result } function makeNode(item: number): node { @@ -17,25 +36,6 @@ function makeNode(item: number): node { let linkedList: list = { head: makeNode(1); - append => (item: number) { - let tail: node = head - while ((tail.next) != null) { - tail = tail.next - } - tail.next = makeNode(item) - }; - getOdd => (): number[] { - let result: number[] - let n = head - while (n != null) { - if ((n.data) % 2 != 0) { - let i = n.data - result = result ++ [i,] - } - n = n.next - } - return result - }; } linkedList.append(3) diff --git a/samples/settable.js b/samples/settable.js index f9b94258..0219a6f3 100644 --- a/samples/settable.js +++ b/samples/settable.js @@ -1,16 +1,16 @@ type settable = { prop: string; - setprop: (string) => void; +} + +function setprop(obj: settable, str: string) { + obj.prop = str + if (obj.prop == "1") { + print("prop is one") + } } let obj: settable = { prop: "prop"; - setprop => (str: string) { - prop = str - if (prop == "1") { - print("prop is one") - } - }; } obj.setprop("1") diff --git a/samples/summator.js b/samples/summator.js index 893aee84..74d21b31 100644 --- a/samples/summator.js +++ b/samples/summator.js @@ -1,9 +1,15 @@ -let summator = { +type summable = { + x: number; + y: number; +} + +function sum(obj: summable): number { + return obj.x + obj.y +} + +let summator: summable = { x: 1; y: 2; - sum => (): number { - return x + y - }; } let s = summator.sum() diff --git a/samples/this.js b/samples/this.js index 6d577351..5b8a3aea 100644 --- a/samples/this.js +++ b/samples/this.js @@ -1,11 +1,18 @@ -let obj = { +type ObjType = { + num: number; + flag: boolean; + str: string; +} + +function toString(obj: ObjType): string { + let s = "object obj:\n" + return s + (obj as string) +} + +let obj: ObjType = { num: 1; flag: true; str: "field"; - toString => (): string { - let s = "object obj:\n" - return s + (this as string) - }; } print(obj.toString()) \ No newline at end of file diff --git a/samples/typeresolving.js b/samples/typeresolving.js new file mode 100644 index 00000000..19730899 --- /dev/null +++ b/samples/typeresolving.js @@ -0,0 +1,14 @@ +type A = { + a: B; +} +{ + type C = string + type D = number[] + type E = A[] + type R = { + prop: R; +} +} +type B = { + b: A; +} \ No newline at end of file diff --git a/samples/vec2d.js b/samples/vec2d.js index bc20bd18..2086ccea 100644 --- a/samples/vec2d.js +++ b/samples/vec2d.js @@ -18,5 +18,6 @@ function vlensquared(v: vec2d): number { let v = makeVec(3, 4) let l = vlensquared(v) - +print(l as string) +l = v.vlensquared() print(l as string) \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/GlobalUsings.cs b/src/Application/HydraScript.Application.CodeGeneration/GlobalUsings.cs new file mode 100644 index 00000000..dcfa2c76 --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/GlobalUsings.cs @@ -0,0 +1,3 @@ +// Global using directives + +global using Visitor.NET; \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/HydraScript.Application.CodeGeneration.csproj b/src/Application/HydraScript.Application.CodeGeneration/HydraScript.Application.CodeGeneration.csproj new file mode 100644 index 00000000..492cf3bd --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/HydraScript.Application.CodeGeneration.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + diff --git a/src/Application/HydraScript.Application.CodeGeneration/ICodeGenerator.cs b/src/Application/HydraScript.Application.CodeGeneration/ICodeGenerator.cs new file mode 100644 index 00000000..0c960e2f --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/ICodeGenerator.cs @@ -0,0 +1,9 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.FrontEnd.Parser; + +namespace HydraScript.Application.CodeGeneration; + +public interface ICodeGenerator +{ + public AddressedInstructions GetInstructions(IAbstractSyntaxTree ast); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs b/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs new file mode 100644 index 00000000..36d70216 --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/IValueDtoConverter.cs @@ -0,0 +1,9 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.CodeGeneration; + +public interface IValueDtoConverter +{ + public IValue Convert(ValueDto dto); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs b/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs new file mode 100644 index 00000000..ee3f919b --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/Impl/ValueDtoConverter.cs @@ -0,0 +1,18 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.CodeGeneration.Impl; + +internal class ValueDtoConverter : IValueDtoConverter +{ + public IValue Convert(ValueDto dto) => + dto switch + { + { Type: ValueDtoType.Constant, Label: not null } => + new Constant(dto.Value, dto.Label), + { Type: ValueDtoType.Name, Name: not null } => + new Name(dto.Name), + _ => throw new ArgumentOutOfRangeException(nameof(dto)) + }; +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs b/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..b379d093 --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using HydraScript.Application.CodeGeneration.Impl; +using HydraScript.Application.CodeGeneration.Visitors; +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.FrontEnd.Parser; +using Microsoft.Extensions.DependencyInjection; + +namespace HydraScript.Application.CodeGeneration; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCodeGeneration(this IServiceCollection services) + { + services.AddSingleton(); + services.AddKeyedSingleton< + IVisitor, + InstructionProvider>("instructions"); + services.AddKeyedSingleton< + IVisitor, + ExpressionInstructionProvider>("expression-instructions"); + return services; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs new file mode 100644 index 00000000..731d085f --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs @@ -0,0 +1,319 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.CodeGeneration.Visitors; + +internal class ExpressionInstructionProvider : VisitorBase, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor +{ + private readonly IValueDtoConverter _valueDtoConverter; + + public ExpressionInstructionProvider(IValueDtoConverter valueDtoConverter) => + _valueDtoConverter = valueDtoConverter; + + public AddressedInstructions Visit(PrimaryExpression visitable) => + [new Simple(_valueDtoConverter.Convert(visitable.ToValueDto()))]; + + public AddressedInstructions Visit(ArrayLiteral visitable) + { + var arraySize = visitable.Expressions.Count; + + var arrayName = visitable.Id; + var createArray = new CreateArray(arrayName, arraySize); + + var result = new AddressedInstructions { createArray }; + + for (var i = 0; i < arraySize; i++) + { + var expression = visitable.Expressions[i]; + var index = new Constant(i); + + if (expression is PrimaryExpression primary) + result.Add(new IndexAssignment( + arrayName, + index, + _valueDtoConverter.Convert(primary.ToValueDto()))); + else + { + result.AddRange(expression.Accept(This)); + var last = new Name(result.OfType().Last().Left!); + result.Add(new IndexAssignment(arrayName, index, last)); + } + } + + return result; + } + + public AddressedInstructions Visit(ObjectLiteral visitable) + { + var objectId = visitable.Id; + var createObject = new CreateObject(objectId); + + var result = new AddressedInstructions { createObject }; + + result.AddRange(visitable.Properties + .SelectMany(property => + property.Accept(This))); + + return result; + } + + public AddressedInstructions Visit(Property visitable) + { + var objectId = visitable.Object.Id; + + var (id, expression) = visitable; + var propertyId = new Constant(id); + + if (expression is PrimaryExpression primary) + return [new DotAssignment( + objectId, + propertyId, + _valueDtoConverter.Convert(primary.ToValueDto()))]; + + var instructions = expression.Accept(This); + var last = new Name(instructions.OfType().Last().Left!); + instructions.Add(new DotAssignment(objectId, propertyId, last)); + + return instructions; + } + + public AddressedInstructions Visit(UnaryExpression visitable) + { + if (visitable.Expression is PrimaryExpression primary) + return [new Simple(visitable.Operator, _valueDtoConverter.Convert(primary.ToValueDto()))]; + + var result = visitable.Expression.Accept(This); + var last = new Name(result.OfType().Last().Left!); + result.Add(new Simple(visitable.Operator, last)); + + return result; + } + + public AddressedInstructions Visit(BinaryExpression visitable) + { + if (visitable is { Left: IdentifierReference arr, Right: PrimaryExpression primary, Operator: "::" }) + return [new RemoveFromArray(arr.Name, index: _valueDtoConverter.Convert(primary.ToValueDto()))]; + + var result = new AddressedInstructions(); + IValue left, right; + + if (visitable.Left is PrimaryExpression primaryLeft) + left = _valueDtoConverter.Convert(primaryLeft.ToValueDto()); + else + { + result.AddRange(visitable.Left.Accept(This)); + left = new Name(result.OfType().Last().Left!); + } + + if (visitable.Right is PrimaryExpression primaryRight) + right = _valueDtoConverter.Convert(primaryRight.ToValueDto()); + else + { + result.AddRange(visitable.Right.Accept(This)); + right = new Name(result.OfType().Last().Left!); + } + + result.Add(new Simple(left, visitable.Operator, right)); + + return result; + } + + public AddressedInstructions Visit(CastAsExpression visitable) + { + if (visitable.Expression is PrimaryExpression primary) + return [new AsString(_valueDtoConverter.Convert(primary.ToValueDto()))]; + + var result = visitable.Expression.Accept(This); + var last = new Name(result.OfType().Last().Left!); + result.Add(new AsString(last)); + + return result; + } + + public AddressedInstructions Visit(ConditionalExpression visitable) + { + var blockId = $"cond_{visitable.GetHashCode()}"; + var startBlockLabel = new Label($"Start_{blockId}"); + var endBlockLabel = new Label($"End_{blockId}"); + + var result = new AddressedInstructions(); + + if (visitable.Test is PrimaryExpression primary) + result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), startBlockLabel)); + else + { + result.AddRange(visitable.Test.Accept(This)); + var last = new Name(result.OfType().Last().Left!); + result.Add(new IfNotGoto(last, startBlockLabel)); + } + + result.AddRange(visitable.Consequent.Accept(This)); + var temp = result.OfType().Last().Left!; + result.Add(new Goto(endBlockLabel)); + + result.Add(new BeginBlock(BlockType.Condition, blockId), startBlockLabel.Name); + result.AddRange(visitable.Alternate.Accept(This)); + result.OfType().Last().Left = temp; + result.Add(new EndBlock(BlockType.Condition, blockId), endBlockLabel.Name); + + result.Add(new Simple(new Name(temp))); + + return result; + } + + public AddressedInstructions Visit(AssignmentExpression visitable) + { + var result = visitable.Source.Accept(This); + if (visitable.Source is AssignmentExpression) + { + var last = result.OfType().Last(); + if (last is IWriteToComplexData assignment) + result.Add(assignment.ToSimple()); + else + result.Add(new Simple(new Name(last.Left!))); + } + + if (visitable.Destination.Empty()) + result.OfType().Last().Left = visitable.Destination.Id; + else + { + var last = new Name(result.OfType().Last().Left!); + result.AddRange(visitable.Destination.Accept(This)); + var lastRead = result.OfType().Last(); + result.Replace(lastRead.ToInstruction(), lastRead.ToAssignment(last)); + } + + return result; + } + + public AddressedInstructions Visit(MemberExpression visitable) => + visitable.Empty() + ? [] + : visitable.Tail?.Accept(This) ?? []; + + public AddressedInstructions Visit(DotAccess visitable) + { + var right = new Constant(visitable.Property.Name); + + if (!visitable.HasPrev() && visitable.Parent is LeftHandSideExpression lhs) + return [new DotRead(new Name(lhs.Id), right)]; + + var result = visitable.Prev?.Accept(This) ?? []; + var left = new Name(result.OfType().Last().Left!); + result.Add(new DotRead(left, right)); + + return result; + } + + public AddressedInstructions Visit(IndexAccess visitable) + { + var result = new AddressedInstructions(); + + IValue right; + + if (visitable.Index is PrimaryExpression primary) + right = _valueDtoConverter.Convert(primary.ToValueDto()); + else + { + result.AddRange(visitable.Index.Accept(This)); + right = new Name(result.OfType().Last().Left!); + } + + if (!visitable.HasPrev() && visitable.Parent is LeftHandSideExpression lhs) + result.Add(new IndexRead(new Name(lhs.Id), right)); + else + { + result.AddRange(visitable.Prev?.Accept(This) ?? []); + var left = new Name(result.OfType().Last().Left!); + result.Add(new IndexRead(left, right)); + } + + return result; + } + + public AddressedInstructions Visit(CallExpression visitable) + { + if (visitable.IsEmptyCall) + return []; + var methodCall = !visitable.Empty(); + if (visitable.Id.Name is "print" && !methodCall) + { + var param = visitable.Parameters[0]; + + if (param is PrimaryExpression prim) + return [new Print(_valueDtoConverter.Convert(prim.ToValueDto()))]; + + var result = param.Accept(This); + var last = new Name(result.OfType().Last().Left!); + result.Add(new Print(last)); + + return result; + } + else + { + string functionId; + AddressedInstructions result = []; + if (methodCall) + { + var memberInstructions = visitable.Member.Accept(This); + var lastMemberInstruction = (DotRead)memberInstructions[memberInstructions.End]; + memberInstructions.Remove(lastMemberInstruction); + result.AddRange(memberInstructions); + + functionId = lastMemberInstruction.Property; + } + else + { + functionId = visitable.Id; + } + var functionInfo = new FunctionInfo(functionId); + + if (methodCall) + { + var caller = result.Any() ? result.OfType().Last().Left! : visitable.Id; + result.Add(new PushParameter(new Name(caller))); + } + foreach (var expr in visitable.Parameters) + { + if (expr is PrimaryExpression primary) + result.Add(new PushParameter(_valueDtoConverter.Convert(primary.ToValueDto()))); + else + { + result.AddRange(expr.Accept(This)); + var id = result.OfType().Last().Left!; + result.Add(new PushParameter(new Name(id))); + } + } + + result.Add(new CallFunction( + functionInfo, + visitable.HasReturnValue)); + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs b/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs new file mode 100644 index 00000000..9a30ebb4 --- /dev/null +++ b/src/Application/HydraScript.Application.CodeGeneration/Visitors/InstructionProvider.cs @@ -0,0 +1,222 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; +using Microsoft.Extensions.DependencyInjection; + +namespace HydraScript.Application.CodeGeneration.Visitors; + +internal class InstructionProvider : VisitorBase, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor +{ + private readonly IValueDtoConverter _valueDtoConverter; + private readonly IVisitor _expressionVisitor; + + public InstructionProvider( + IValueDtoConverter valueDtoConverter, + [FromKeyedServices("expression-instructions")] + IVisitor expressionVisitor) + { + _valueDtoConverter = valueDtoConverter; + _expressionVisitor = expressionVisitor; + } + + public AddressedInstructions Visit(ScriptBody visitable) + { + var result = new AddressedInstructions(); + for (var i = 0; i < visitable.Count; i++) + { + var instructions = visitable[i].Accept(This); + result.AddRange(instructions); + } + + result.Add(new Halt()); + + return result; + } + + public override AddressedInstructions DefaultVisit { get; } = []; + + public AddressedInstructions Visit(LexicalDeclaration visitable) + { + var result = new AddressedInstructions(); + for (var i = 0; i < visitable.Count; i++) + { + var assignment = visitable[i]; + result.AddRange(assignment.Accept(_expressionVisitor)); + } + + return result; + } + + public AddressedInstructions Visit(BlockStatement visitable) + { + var result = new AddressedInstructions(); + for (var i = 0; i < visitable.Count; i++) + { + var item = visitable[i]; + result.AddRange(item.Accept(This)); + if (item is ReturnStatement) break; + } + + return result; + } + + public AddressedInstructions Visit(InsideStatementJump visitable) + { + var jumpType = visitable.Keyword switch + { + InsideStatementJump.Break => InsideStatementJumpType.Break, + InsideStatementJump.Continue => InsideStatementJumpType.Continue, + _ => throw new ArgumentOutOfRangeException( + nameof(visitable.Keyword), visitable.Keyword, + "Unsupported keyword inside loop") + }; + + return [new Goto(jumpType)]; + } + + public AddressedInstructions Visit(ExpressionStatement visitable) => + visitable.Expression.Accept(_expressionVisitor); + + public AddressedInstructions Visit(ReturnStatement visitable) + { + switch (visitable.Expression) + { + case null: + return [new Return()]; + case PrimaryExpression primary: + return [new Return(_valueDtoConverter.Convert(primary.ToValueDto()))]; + } + + var result = visitable.Expression.Accept(_expressionVisitor); + var last = new Name(result.OfType().Last().Left!); + result.Add(new Return(last)); + + return result; + } + + public AddressedInstructions Visit(FunctionDeclaration visitable) + { + if (!visitable.Statements.Any()) + return []; + + var functionInfo = new FunctionInfo(visitable.Name); + + var result = new AddressedInstructions + { + new Goto(functionInfo.End), + { + new BeginBlock(BlockType.Function, blockId: functionInfo.ToString()), + functionInfo.Start.Name + } + }; + + foreach (var (id, _) in visitable.Arguments) + result.Add(new PopParameter(id)); + + result.AddRange(visitable.Statements.Accept(This)); + if (!visitable.HasReturnStatement()) + result.Add(new Return()); + + result.Add(new EndBlock(BlockType.Function, blockId: functionInfo.ToString()), functionInfo.End.Name); + + return result; + } + + public AddressedInstructions Visit(WhileStatement visitable) + { + var blockId = $"while_{visitable.GetHashCode()}"; + var startBlockLabel = new Label($"Start_{blockId}"); + var endBlockLabel = new Label($"End_{blockId}"); + + var result = new AddressedInstructions + { + { new BeginBlock(BlockType.Loop, blockId), startBlockLabel.Name } + }; + + if (visitable.Condition is PrimaryExpression primary) + result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), endBlockLabel)); + else + { + result.AddRange(visitable.Condition.Accept(_expressionVisitor)); + var last = new Name(result.OfType().Last().Left!); + result.Add(new IfNotGoto(last, endBlockLabel)); + } + + result.AddRange(visitable.Statement.Accept(This)); + result.OfType().Where(g => g.JumpType is not null) + .ToList().ForEach(g => + { + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (g.JumpType) + { + case InsideStatementJumpType.Break: + g.SetJump(endBlockLabel); + break; + case InsideStatementJumpType.Continue: + g.SetJump(startBlockLabel); + break; + } + }); + result.Add(new Goto(startBlockLabel)); + + result.Add(new EndBlock(BlockType.Loop, blockId), endBlockLabel.Name); + + return result; + } + + public AddressedInstructions Visit(IfStatement visitable) + { + if (visitable.Empty()) + return []; + + var blockId = $"if_else_{visitable.GetHashCode()}"; + var startBlockLabel = new Label($"Start_{blockId}"); + var endBlockLabel = new Label($"End_{blockId}"); + + var result = new AddressedInstructions(); + + if (visitable.Test is PrimaryExpression primary) + result.Add(new IfNotGoto(test: _valueDtoConverter.Convert(primary.ToValueDto()), startBlockLabel)); + else + { + result.AddRange(visitable.Test.Accept(_expressionVisitor)); + var last = new Name(result.OfType().Last().Left!); + result.Add(new IfNotGoto(last, + visitable.HasElseBlock() + ? startBlockLabel + : endBlockLabel) + ); + } + + result.AddRange(visitable.Then.Accept(This)); + result.Add(new Goto(endBlockLabel)); + result.Add(new BeginBlock(BlockType.Condition, blockId), startBlockLabel.Name); + + if (visitable.HasElseBlock()) + result.AddRange(visitable.Else?.Accept(This) ?? []); + + result.OfType().Where(g => g.JumpType is InsideStatementJumpType.Break) + .ToList().ForEach(g => g.SetJump(endBlockLabel)); + + result.Add(new EndBlock(BlockType.Condition, blockId), endBlockLabel.Name); + + return result; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ArrayAccessException.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ArrayAccessException.cs new file mode 100644 index 00000000..e442bc98 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ArrayAccessException.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class ArrayAccessException(string segment, Type type) : SemanticException( + segment, + $"Array element cannot be accessed with type {type} it must be of type number"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/AssignmentToConst.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/AssignmentToConst.cs new file mode 100644 index 00000000..f78397b2 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/AssignmentToConst.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class AssignmentToConst(IdentifierReference ident) + : SemanticException(ident.Segment, $"Cannot assign to const: {ident.Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/CannotDefineType.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/CannotDefineType.cs new file mode 100644 index 00000000..6c568f49 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/CannotDefineType.cs @@ -0,0 +1,6 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class CannotDefineType(string segment) : SemanticException(segment, "Cannot define type"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ConstWithoutInitializer.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ConstWithoutInitializer.cs new file mode 100644 index 00000000..b510b18b --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ConstWithoutInitializer.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class ConstWithoutInitializer(IdentifierReference ident) : + SemanticException(ident.Segment, $"'const' without initializer: {ident.Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/DeclarationAlreadyExists.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/DeclarationAlreadyExists.cs new file mode 100644 index 00000000..82ca2b0f --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/DeclarationAlreadyExists.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class DeclarationAlreadyExists(IdentifierReference ident) : + SemanticException(ident.Segment, $"Declaration already exists: {ident.Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/FunctionWithoutReturnStatement.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/FunctionWithoutReturnStatement.cs new file mode 100644 index 00000000..5289cc1b --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/FunctionWithoutReturnStatement.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class FunctionWithoutReturnStatement(string segment) : + SemanticException(segment, "function with non-void return type must have a return statement"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/IncompatibleTypesOfOperands.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/IncompatibleTypesOfOperands.cs new file mode 100644 index 00000000..7018af53 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/IncompatibleTypesOfOperands.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class IncompatibleTypesOfOperands(string segment, Type left, Type right) : + SemanticException(segment, $"Incompatible types of operands: {left} and {right}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NonAccessibleType.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NonAccessibleType.cs new file mode 100644 index 00000000..2dea4327 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NonAccessibleType.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class NonAccessibleType(Type type) : + SemanticException($"Type '{type}' is not array-like or object-like"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NotBooleanTestExpression.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NotBooleanTestExpression.cs new file mode 100644 index 00000000..e06bb7ff --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/NotBooleanTestExpression.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class NotBooleanTestExpression(string segment, Type type) : + SemanticException(segment, $"Type of expression is {type} but expected boolean"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ObjectAccessException.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ObjectAccessException.cs new file mode 100644 index 00000000..c937d928 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ObjectAccessException.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class ObjectAccessException(string segment, ObjectType objectType, string field) : + SemanticException(segment, $"Object type {objectType} has no field {field}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/OutsideOfStatement.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/OutsideOfStatement.cs new file mode 100644 index 00000000..b624ba13 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/OutsideOfStatement.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class OutsideOfStatement(string segment, string keyword, string statement) : + SemanticException(segment, $"Jump \"{keyword}\" outside of statement \"{statement}\""); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ReturnOutsideFunction.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ReturnOutsideFunction.cs new file mode 100644 index 00000000..1f095a0b --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/ReturnOutsideFunction.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class ReturnOutsideFunction(string segment) : + SemanticException(segment, "\"return\" outside function"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SemanticException.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SemanticException.cs new file mode 100644 index 00000000..f68e4ec7 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SemanticException.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[Serializable, ExcludeFromCodeCoverage] +public abstract class SemanticException : Exception +{ + protected SemanticException() + { + } + + protected SemanticException(string message) : base(message) + { + } + + protected SemanticException(string message, Exception inner) : base(message, inner) + { + } + + protected SemanticException(string segment, string message) : + base($"{segment} {message}") + { + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SymbolIsNotCallable.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SymbolIsNotCallable.cs new file mode 100644 index 00000000..e11ec498 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/SymbolIsNotCallable.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class SymbolIsNotCallable(string symbol, string segment) : + SemanticException(segment, $"Symbol is not callable: {symbol}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnknownIdentifierReference.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnknownIdentifierReference.cs new file mode 100644 index 00000000..e12fb35c --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnknownIdentifierReference.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class UnknownIdentifierReference(IdentifierReference ident) : + SemanticException(ident.Segment, $"Unknown identifier reference: {ident.Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnsupportedOperation.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnsupportedOperation.cs new file mode 100644 index 00000000..028e386e --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/UnsupportedOperation.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class UnsupportedOperation(string segment, Type type, string @operator) : + SemanticException(segment, $"Type {type} does not support operation {@operator}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongArrayLiteralDeclaration.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongArrayLiteralDeclaration.cs new file mode 100644 index 00000000..bef135e8 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongArrayLiteralDeclaration.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongArrayLiteralDeclaration(string segment, Type type) : + SemanticException( + segment, + $"Wrong array literal declaration: all array elements must be of type {type}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs new file mode 100644 index 00000000..2f781f57 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongAssignmentTarget(LeftHandSideExpression lhs) : + SemanticException( + lhs.Segment, + $"Assignment target must be variable, property or indexer. '{lhs.Id.Name}' is {lhs.GetType().Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongConditionalTypes.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongConditionalTypes.cs new file mode 100644 index 00000000..57a90f95 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongConditionalTypes.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongConditionalTypes( + string segment, + string cSegment, + Type cType, + string aSegment, + Type aType) : SemanticException( + segment, + $"Different types in conditional: {cSegment} consequent - {cType}, {aSegment} alternate {aType}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongNumberOfArguments.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongNumberOfArguments.cs new file mode 100644 index 00000000..b77f7337 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongNumberOfArguments.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongNumberOfArguments(string segment, int expected, int actual) : + SemanticException( + segment, + $"Wrong number of arguments: expected {expected}, actual {actual}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongReturnType.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongReturnType.cs new file mode 100644 index 00000000..c204f652 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongReturnType.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongReturnType(string segment, Type expected, Type actual) : + SemanticException( + segment, + $"Wrong return type: expected {expected}, actual {actual}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongTypeOfArgument.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongTypeOfArgument.cs new file mode 100644 index 00000000..b89010be --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongTypeOfArgument.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Application.StaticAnalysis.Exceptions; + +[ExcludeFromCodeCoverage] +public class WrongTypeOfArgument(string segment, Type expected, Type actual) : + SemanticException( + segment, + $"Wrong type of argument: expected {expected}, actual {actual}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/GlobalUsings.cs b/src/Application/HydraScript.Application.StaticAnalysis/GlobalUsings.cs new file mode 100644 index 00000000..e00ba324 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/GlobalUsings.cs @@ -0,0 +1,4 @@ +// Global using directives + +global using Type = HydraScript.Domain.IR.Types.Type; +global using Visitor.NET; \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/HydraScript.Application.StaticAnalysis.csproj b/src/Application/HydraScript.Application.StaticAnalysis/HydraScript.Application.StaticAnalysis.csproj new file mode 100644 index 00000000..75c575c1 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/HydraScript.Application.StaticAnalysis.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs new file mode 100644 index 00000000..a6ea02e1 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Application.StaticAnalysis; + +public interface IComputedTypesStorage +{ + public Guid Save(Type computedType); + + public Type Get(Guid computedTypeGuid); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs new file mode 100644 index 00000000..ba34c898 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Application.StaticAnalysis; + +public interface IDefaultValueForTypeCalculator +{ + public object? GetDefaultValueForType(Type type); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IFunctionWithUndefinedReturnStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/IFunctionWithUndefinedReturnStorage.cs new file mode 100644 index 00000000..0c725a8c --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IFunctionWithUndefinedReturnStorage.cs @@ -0,0 +1,15 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.IR.Impl.Symbols; + +namespace HydraScript.Application.StaticAnalysis; + +public interface IFunctionWithUndefinedReturnStorage +{ + public void Save(FunctionSymbol symbol, FunctionDeclaration declaration); + + public FunctionDeclaration Get(FunctionSymbol symbol); + + public void RemoveIfPresent(FunctionSymbol symbol); + + public IEnumerable Flush(); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs new file mode 100644 index 00000000..42e79dac --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Application.StaticAnalysis; + +public interface IJavaScriptTypesProvider +{ + public IEnumerable GetDefaultTypes(); + + public bool Contains(Type type); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IMethodStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/IMethodStorage.cs new file mode 100644 index 00000000..9adf811d --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IMethodStorage.cs @@ -0,0 +1,11 @@ +using HydraScript.Domain.IR.Impl.Symbols; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis; + +public interface IMethodStorage +{ + public void BindMethod(ObjectType objectType, FunctionSymbol method); + + public IReadOnlyDictionary GetAvailableMethods(ObjectType objectType); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IStandardLibraryProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IStandardLibraryProvider.cs new file mode 100644 index 00000000..5b83c00b --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IStandardLibraryProvider.cs @@ -0,0 +1,8 @@ +using HydraScript.Domain.IR; + +namespace HydraScript.Application.StaticAnalysis; + +public interface IStandardLibraryProvider +{ + public ISymbolTable GetStandardLibrary(); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IStaticAnalyzer.cs b/src/Application/HydraScript.Application.StaticAnalysis/IStaticAnalyzer.cs new file mode 100644 index 00000000..d4ee79c0 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IStaticAnalyzer.cs @@ -0,0 +1,8 @@ +using HydraScript.Domain.FrontEnd.Parser; + +namespace HydraScript.Application.StaticAnalysis; + +public interface IStaticAnalyzer +{ + public void Analyze(IAbstractSyntaxTree ast); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ISymbolTableStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/ISymbolTableStorage.cs new file mode 100644 index 00000000..038669e0 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/ISymbolTableStorage.cs @@ -0,0 +1,13 @@ +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.IR; + +namespace HydraScript.Application.StaticAnalysis; + +public interface ISymbolTableStorage +{ + public ISymbolTable this[Scope scope] { get; } + + public void Init(Scope scope, ISymbolTable symbolTable); + + public void InitWithOpenScope(Scope scope); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs new file mode 100644 index 00000000..d7ac8ff2 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs @@ -0,0 +1,10 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Application.StaticAnalysis; + +public interface ITypeDeclarationsResolver +{ + public void Store(TypeDeclaration declaration); + + public void Resolve(); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs new file mode 100644 index 00000000..f38ff265 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class ComputedTypesStorage : IComputedTypesStorage +{ + private readonly Dictionary _computedTypes = []; + + public Guid Save(Type computedType) + { + var guid = Guid.NewGuid(); + _computedTypes[guid] = computedType; + return guid; + } + + public Type Get(Guid computedTypeGuid) => + _computedTypes[computedTypeGuid]; +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs new file mode 100644 index 00000000..c11624c7 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs @@ -0,0 +1,30 @@ +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class DefaultValueForTypeCalculator : IDefaultValueForTypeCalculator +{ + private readonly Type _boolean = "boolean"; + private readonly Type _number = "number"; + private readonly Type _string = "string"; + private readonly Type _void = "void"; + private readonly Type _null = new NullType(); + + public object? GetDefaultValueForType(Type type) + { + if (type.Equals(_boolean)) + return false; + if (type.Equals(_number)) + return 0; + if (type.Equals(_string)) + return string.Empty; + if (type.Equals(_void)) + return new object(); + if (type.Equals(_null)) + return null; + if (type is ArrayType) + return new List(); + + return new object(); + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/FunctionWithUndefinedReturnStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/FunctionWithUndefinedReturnStorage.cs new file mode 100644 index 00000000..4de6ff44 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/FunctionWithUndefinedReturnStorage.cs @@ -0,0 +1,40 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.IR.Impl.Symbols; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class FunctionWithUndefinedReturnStorage : IFunctionWithUndefinedReturnStorage +{ + private readonly Dictionary _declarations = []; + private readonly Dictionary _keysWithOrder = []; + + public void Save(FunctionSymbol symbol, FunctionDeclaration declaration) + { + _declarations[symbol.Id] = declaration; + _keysWithOrder[symbol.Id] = _declarations.Count; + } + + public FunctionDeclaration Get(FunctionSymbol symbol) + { + if (!_declarations.Remove(symbol.Id, out var declaration)) + throw new InvalidOperationException(message: "Cannot get function that has not been saved"); + + _keysWithOrder.Remove(symbol.Id); + return declaration; + } + + public void RemoveIfPresent(FunctionSymbol symbol) + { + _declarations.Remove(symbol.Id); + _keysWithOrder.Remove(symbol.Id); + } + + public IEnumerable Flush() => _declarations + .OrderBy(kvp => _keysWithOrder[kvp.Key]) + .Select(x => + { + _declarations.Remove(x.Key); + _keysWithOrder.Remove(x.Key); + return x.Value; + }); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs new file mode 100644 index 00000000..425a08a5 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class JavaScriptTypesProvider : IJavaScriptTypesProvider +{ + private readonly HashSet _types = + [ + "number", + "boolean", + "string", + new NullType(), + "undefined", + "void" + ]; + + public IEnumerable GetDefaultTypes() => _types; + + public bool Contains(Type type) => _types.Contains(type); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/MethodStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/MethodStorage.cs new file mode 100644 index 00000000..7e480be8 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/MethodStorage.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.IR.Impl.Symbols; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class MethodStorage : IMethodStorage +{ + private readonly Dictionary> _bindings = []; + + public void BindMethod(ObjectType objectType, FunctionSymbol method) + { + objectType.AddMethod(method.Id); + if (!_bindings.ContainsKey(objectType)) + _bindings[objectType] = new Dictionary(); + _bindings[objectType][method.Id] = method; + } + + public IReadOnlyDictionary GetAvailableMethods(ObjectType objectType) => + _bindings.GetValueOrDefault(objectType, new Dictionary()); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs new file mode 100644 index 00000000..68f0dd76 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs @@ -0,0 +1,33 @@ +using HydraScript.Domain.IR; +using HydraScript.Domain.IR.Impl; +using HydraScript.Domain.IR.Impl.Symbols; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class StandardLibraryProvider : IStandardLibraryProvider +{ + private readonly IJavaScriptTypesProvider _provider; + + public StandardLibraryProvider(IJavaScriptTypesProvider provider) => + _provider = provider; + + public ISymbolTable GetStandardLibrary() + { + var library = new SymbolTable(); + + foreach (var type in _provider.GetDefaultTypes()) + library.AddSymbol(new TypeSymbol(type)); + + var print = new FunctionSymbol( + "print", + [new VariableSymbol("str", "string")], + "void", + isEmpty: false); + + library.AddSymbol(print); + + var symbolTable = new SymbolTable(); + symbolTable.AddOpenScope(library); + return symbolTable; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/SymbolTableStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/SymbolTableStorage.cs new file mode 100644 index 00000000..c46b841e --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/SymbolTableStorage.cs @@ -0,0 +1,24 @@ +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.IR; +using HydraScript.Domain.IR.Impl; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class SymbolTableStorage : ISymbolTableStorage +{ + private readonly Dictionary _symbolTables = []; + public ISymbolTable this[Scope scope] => _symbolTables[scope.Id]; + + public void Init(Scope scope, ISymbolTable symbolTable) => + _symbolTables.TryAdd(scope.Id, symbolTable); + + public void InitWithOpenScope(Scope scope) + { + ArgumentNullException.ThrowIfNull(scope.OpenScope); + if (_symbolTables.ContainsKey(scope.Id)) + return; + var symbolTable = new SymbolTable(); + symbolTable.AddOpenScope(_symbolTables[scope.OpenScope.Id]); + _symbolTables[scope.Id] = symbolTable; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs new file mode 100644 index 00000000..93a8a5f5 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs @@ -0,0 +1,61 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.IR.Impl.Symbols; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class TypeDeclarationsResolver : ITypeDeclarationsResolver +{ + private readonly Queue _declarationsToResolve = []; + + private readonly IJavaScriptTypesProvider _provider; + private readonly ISymbolTableStorage _symbolTables; + private readonly IVisitor _typeBuilder; + + public TypeDeclarationsResolver( + IJavaScriptTypesProvider provider, + ISymbolTableStorage symbolTables, + IVisitor typeBuilder) + { + _provider = provider; + _symbolTables = symbolTables; + _typeBuilder = typeBuilder; + } + + public void Store(TypeDeclaration declaration) => + _declarationsToResolve.Enqueue(declaration); + + public void Resolve() + { + var defaults = _provider.GetDefaultTypes() + .Select(x => new TypeSymbol(x)) + .ToList(); + + foreach (var declarationToResolve in _declarationsToResolve) + { + _symbolTables[declarationToResolve.Scope].AddSymbol( + new TypeSymbol( + declarationToResolve.TypeValue.Accept(_typeBuilder), + declarationToResolve.TypeId)); + } + + while (_declarationsToResolve.Count != 0) + { + var declarationToResolve = _declarationsToResolve.Dequeue(); + + var typeSymbol = _symbolTables[declarationToResolve.Scope] + .FindSymbol(declarationToResolve.TypeId)!; + + var resolvingCandidates = _symbolTables[declarationToResolve.Scope] + .GetAvailableSymbols() + .OfType() + .Except(defaults); + + foreach (var referenceSymbol in resolvingCandidates) + { + typeSymbol.Type.ResolveReference( + referenceSymbol.Type, + referenceSymbol.Id); + } + } + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..7766aa4b --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs @@ -0,0 +1,34 @@ +using HydraScript.Application.StaticAnalysis.Impl; +using HydraScript.Application.StaticAnalysis.Visitors; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using Microsoft.Extensions.DependencyInjection; + +namespace HydraScript.Application.StaticAnalysis; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddStaticAnalysis(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton, TypeBuilder>(); + + services.AddSingleton, SymbolTableInitializer>(); + services.AddSingleton, TypeSystemLoader>(); + services.AddSingleton, DeclarationVisitor>(); + + services.AddSingleton, SemanticChecker>(); + + return services; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs new file mode 100644 index 00000000..0b05d4e8 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs @@ -0,0 +1,104 @@ +using HydraScript.Application.StaticAnalysis.Exceptions; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.IR.Impl.Symbols; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Visitors; + +internal class DeclarationVisitor : VisitorNoReturnBase, + IVisitor, + IVisitor +{ + private readonly IFunctionWithUndefinedReturnStorage _functionStorage; + private readonly IMethodStorage _methodStorage; + private readonly ISymbolTableStorage _symbolTables; + private readonly IVisitor _typeBuilder; + + public DeclarationVisitor( + IFunctionWithUndefinedReturnStorage functionStorage, + IMethodStorage methodStorage, + ISymbolTableStorage symbolTables, + IVisitor typeBuilder) + { + _functionStorage = functionStorage; + _methodStorage = methodStorage; + _symbolTables = symbolTables; + _typeBuilder = typeBuilder; + } + + public override VisitUnit Visit(IAbstractSyntaxTreeNode visitable) + { + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + + return default; + } + + public VisitUnit Visit(LexicalDeclaration visitable) + { + for (var i = 0; i < visitable.Assignments.Count; i++) + { + var assignment = visitable.Assignments[i]; + if (_symbolTables[visitable.Scope].ContainsSymbol(assignment.Destination.Id)) + throw new DeclarationAlreadyExists(assignment.Destination.Id); + + var destinationType = assignment.DestinationType?.Accept( + _typeBuilder) ?? "undefined"; + + if (destinationType == "undefined" && + assignment.Source is ImplicitLiteral or ArrayLiteral { Expressions.Count: 0 }) + throw visitable.ReadOnly + ? new ConstWithoutInitializer(assignment.Destination.Id) + : new CannotDefineType(assignment.Destination.Id.Segment); + + _symbolTables[visitable.Scope].AddSymbol( + new VariableSymbol( + assignment.Destination.Id, + destinationType)); + } + + return default; + } + + public VisitUnit Visit(FunctionDeclaration visitable) + { + if (_symbolTables[visitable.Parent.Scope].ContainsSymbol(visitable.Name)) + throw new DeclarationAlreadyExists(visitable.Name); + + var parameters = visitable.Arguments.Select(x => + { + var arg = new VariableSymbol( + id: x.Key, + x.TypeValue.Accept(_typeBuilder)); + _symbolTables[visitable.Scope].AddSymbol(arg); + return arg; + }).ToList(); + + var functionSymbol = new FunctionSymbol( + visitable.Name, + parameters, + visitable.ReturnTypeValue.Accept(_typeBuilder), + visitable.IsEmpty); + if (parameters is [{ Type: ObjectType objectType }, ..] && + visitable.Arguments is [{ TypeValue: TypeIdentValue }, ..]) + { + _methodStorage.BindMethod(objectType, functionSymbol); + } + + Type undefined = "undefined"; + if (functionSymbol.Type.Equals(undefined)) + { + if (visitable.HasReturnStatement()) + _functionStorage.Save(functionSymbol, visitable); + else + functionSymbol.DefineReturnType("void"); + } + + _symbolTables[visitable.Parent.Scope].AddSymbol(functionSymbol); + return visitable.Statements.Accept(This); + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs new file mode 100644 index 00000000..f8be762e --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -0,0 +1,485 @@ +using HydraScript.Application.StaticAnalysis.Exceptions; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; +using HydraScript.Domain.IR; +using HydraScript.Domain.IR.Impl.Symbols; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Visitors; + +internal class SemanticChecker : VisitorBase, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor, + IVisitor +{ + private readonly IDefaultValueForTypeCalculator _calculator; + private readonly IFunctionWithUndefinedReturnStorage _functionStorage; + private readonly IMethodStorage _methodStorage; + private readonly ISymbolTableStorage _symbolTables; + private readonly IComputedTypesStorage _computedTypes; + private readonly IVisitor _typeBuilder; + + public SemanticChecker( + IDefaultValueForTypeCalculator calculator, + IFunctionWithUndefinedReturnStorage functionStorage, + IMethodStorage methodStorage, + ISymbolTableStorage symbolTables, + IComputedTypesStorage computedTypes, + IVisitor typeBuilder) + { + _calculator = calculator; + _functionStorage = functionStorage; + _methodStorage = methodStorage; + _symbolTables = symbolTables; + _computedTypes = computedTypes; + _typeBuilder = typeBuilder; + } + + public override Type DefaultVisit => "undefined"; + + public Type Visit(ScriptBody visitable) + { + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + + foreach (var funcDecl in _functionStorage.Flush()) + funcDecl.Accept(This); + + return "undefined"; + } + + public Type Visit(WhileStatement visitable) + { + var condType = visitable.Condition.Accept(This); + Type boolean = "boolean"; + if (!condType.Equals(boolean)) + throw new NotBooleanTestExpression(visitable.Segment, condType); + + visitable.Statement.Accept(This); + + return "undefined"; + } + + public Type Visit(IfStatement visitable) + { + var testType = visitable.Test.Accept(This); + Type boolean = "boolean"; + if (!testType.Equals(boolean)) + throw new NotBooleanTestExpression(visitable.Segment, testType); + + visitable.Then.Accept(This); + visitable.Else?.Accept(This); + + return "undefined"; + } + + public Type Visit(InsideStatementJump visitable) + { + switch (visitable.Keyword) + { + case InsideStatementJump.Break: + if (!(visitable.ChildOf() || visitable.ChildOf())) + throw new OutsideOfStatement( + visitable.Segment, + keyword: InsideStatementJump.Break, + statement: "if|while"); + break; + case InsideStatementJump.Continue: + if (!visitable.ChildOf()) + throw new OutsideOfStatement( + visitable.Segment, + keyword: InsideStatementJump.Continue, + statement: "while"); + break; + } + + return "undefined"; + } + + public Type Visit(ReturnStatement visitable) + { + if (!visitable.ChildOf()) + throw new ReturnOutsideFunction(visitable.Segment); + + return visitable.Expression?.Accept(This) ?? "void"; + } + + public Type Visit(ExpressionStatement visitable) => + visitable.Expression.Accept(This); + + public Type Visit(IdentifierReference visitable) + { + var symbol = _symbolTables[visitable.Scope].FindSymbol(visitable.Name); + return symbol?.Type ?? throw new UnknownIdentifierReference(visitable); + } + + public Type Visit(Literal visitable) => + visitable.Type.Accept(_typeBuilder); + + public Type Visit(ImplicitLiteral visitable) + { + var type = visitable.Type.Accept(_typeBuilder); + visitable.ComputedDefaultValue = _calculator.GetDefaultValueForType(type); + return type; + } + + public Type Visit(ArrayLiteral visitable) + { + if (visitable.Expressions.Count == 0) + return new ArrayType(new Any()); + + var type = visitable.First().Accept(This); + if (visitable.Expressions.All(e => e.Accept(This).Equals(type))) + return new ArrayType(type); + + throw new WrongArrayLiteralDeclaration(visitable.Segment, type); + } + + public Type Visit(ObjectLiteral visitable) + { + var properties = visitable.Properties.Select(prop => + { + var propType = prop.Expression.Accept(This); + _symbolTables[visitable.Scope].AddSymbol(propType switch + { + ObjectType objectType => new ObjectSymbol(prop.Id, objectType), + _ => new VariableSymbol(prop.Id, propType) + }); + return new PropertyType(prop.Id, propType); + }); + var objectLiteralType = new ObjectType(properties); + return objectLiteralType; + } + + public Type Visit(ConditionalExpression visitable) + { + var tType = visitable.Test.Accept(This); + Type boolean = "boolean"; + if (!tType.Equals(boolean)) + throw new NotBooleanTestExpression(visitable.Test.Segment, tType); + + var cType = visitable.Consequent.Accept(This); + var aType = visitable.Alternate.Accept(This); + if (cType.Equals(aType)) + return cType; + + throw new WrongConditionalTypes( + segment: visitable.Segment, + cSegment: visitable.Consequent.Segment, + cType, + aSegment: visitable.Alternate.Segment, + aType); + } + + public Type Visit(BinaryExpression visitable) + { + var lType = visitable.Left.Accept(This); + var rType = visitable.Right.Accept(This); + + if (visitable.Operator != "::" && !lType.Equals(rType)) + throw new IncompatibleTypesOfOperands( + visitable.Segment, + left: lType, + right: rType); + + Type number = "number"; + Type @string = "string"; + Type boolean = "boolean"; + + return visitable.Operator switch + { + "+" when lType.Equals(number) => number, + "+" when lType.Equals(@string) => @string, + "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "-" or "*" or "/" or "%" => lType.Equals(number) + ? number + : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "||" or "&&" => lType.Equals(boolean) + ? boolean + : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "==" or "!=" => boolean, + ">" or ">=" or "<" or "<=" => lType.Equals(number) + ? boolean + : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "++" when lType is ArrayType { Type: Any } && rType is ArrayType { Type: Any } => + throw new CannotDefineType(visitable.Segment), + "++" => lType is ArrayType lArrType && rType is ArrayType rArrType + ? new List { lArrType, rArrType }.First(x => x.Type is not Any) + : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "::" when lType is not ArrayType => + throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), + "::" => rType.Equals(number) ? "void" : throw new ArrayAccessException(visitable.Segment, rType), + _ => "undefined" + }; + } + + public Type Visit(UnaryExpression visitable) + { + var eType = visitable.Expression.Accept(This); + + Type number = "number"; + Type boolean = "boolean"; + + return visitable.Operator switch + { + "-" when eType.Equals(number) => number, + "!" when eType.Equals(boolean) => boolean, + "~" when eType is ArrayType => number, + _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) + }; + } + + public Type Visit(LexicalDeclaration visitable) + { + Type undefined = "undefined"; + + for (var i = 0; i < visitable.Assignments.Count; i++) + { + var assignment = visitable.Assignments[i]; + var registeredSymbol = _symbolTables[visitable.Scope].FindSymbol( + assignment.Destination.Id); + var sourceType = assignment.Source.Accept(This); + if (sourceType.Equals(undefined)) + throw new CannotDefineType(assignment.Source.Segment); + if (!registeredSymbol!.Type.Equals(undefined) && !registeredSymbol.Type.Equals(sourceType)) + throw new IncompatibleTypesOfOperands( + assignment.Segment, + left: registeredSymbol.Type, + right: sourceType); + + var actualType = registeredSymbol.Type.Equals(undefined) + ? sourceType + : registeredSymbol.Type; + var actualSymbol = actualType switch + { + ObjectType objectType => new ObjectSymbol(registeredSymbol.Id, objectType, visitable.ReadOnly), + _ => new VariableSymbol(registeredSymbol.Id, actualType, visitable.ReadOnly) + }; + _symbolTables[visitable.Scope].AddSymbol(actualSymbol); + } + + return undefined; + } + + public Type Visit(AssignmentExpression visitable) + { + if (visitable.Destination is CallExpression) + throw new WrongAssignmentTarget(visitable.Destination); + + var sourceType = visitable.Source.Accept(This); + if (!visitable.Destination.Empty()) + { + var destinationType = visitable.Destination.Accept(This); + if (!destinationType.Equals(sourceType)) + throw new IncompatibleTypesOfOperands( + visitable.Segment, + left: destinationType, + right: sourceType); + return destinationType; + } + + var symbol = + _symbolTables[visitable.Scope].FindSymbol( + visitable.Destination.Id) ?? + throw new UnknownIdentifierReference(visitable.Destination.Id); + + if (symbol.ReadOnly) + throw new AssignmentToConst(visitable.Destination.Id); + + if (!sourceType.Equals(symbol.Type)) + throw new IncompatibleTypesOfOperands( + visitable.Segment, + left: symbol.Type, + right: sourceType); + + return symbol.Type; + } + + public Type Visit(MemberExpression visitable) + { + var idType = visitable.Id.Accept(This); + visitable.ComputedIdTypeGuid = _computedTypes.Save(idType); + return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? "undefined"; + } + + public Type Visit(IndexAccess visitable) + { + var prevTypeGuid = + visitable.Prev?.ComputedTypeGuid + ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; + var prevType = _computedTypes.Get(prevTypeGuid); + + if (prevType is not ArrayType arrayType) + throw new NonAccessibleType(prevType); + + Type number = "number"; + var indexType = visitable.Index.Accept(This); + if (!indexType.Equals(number)) + throw new ArrayAccessException(visitable.Segment, indexType); + + var elemType = arrayType.Type; + visitable.ComputedTypeGuid = _computedTypes.Save(elemType); + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : elemType; + } + + public Type Visit(DotAccess visitable) + { + var prevTypeGuid = + visitable.Prev?.ComputedTypeGuid + ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; + var prevType = _computedTypes.Get(prevTypeGuid); + + if (prevType is not ObjectType objectType) + throw new NonAccessibleType(prevType); + + var fieldType = objectType[visitable.Property]; + var hasMethod = objectType.HasMethod(visitable.Property); + if (fieldType is null) + return hasMethod + ? objectType + : throw new ObjectAccessException(visitable.Segment, objectType, visitable.Property); + visitable.ComputedTypeGuid = _computedTypes.Save(fieldType); + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : fieldType; + } + + public Type Visit(CastAsExpression visitable) + { + Type undefined = "undefined"; + var exprType = visitable.Expression.Accept(This); + + if (exprType.Equals(undefined)) + throw new CannotDefineType(visitable.Expression.Segment); + + return visitable.Cast.Accept(_typeBuilder) == "string" + ? "string" + : throw new NotSupportedException("Other types but 'string' have not been supported for casting yet"); + } + + public Type Visit(CallExpression visitable) + { + FunctionSymbol functionSymbol; + var methodCall = !visitable.Member.Empty(); + + if (methodCall) + { + var objectType = (ObjectType)visitable.Member.Accept(This); + var availableMethods = _methodStorage.GetAvailableMethods(objectType); + functionSymbol = availableMethods[objectType.LastAccessedMethod]; + } + else + { + var symbol = + _symbolTables[visitable.Scope].FindSymbol(visitable.Id) + ?? throw new UnknownIdentifierReference(visitable.Id); + functionSymbol = + symbol as FunctionSymbol + ?? throw new SymbolIsNotCallable(symbol.Id, visitable.Id.Segment); + } + + visitable.IsEmptyCall = functionSymbol.IsEmpty; + var functionReturnType = functionSymbol.Type; + + if (functionSymbol.Parameters.Count != visitable.Parameters.Count + (methodCall ? 1 : 0)) + throw new WrongNumberOfArguments( + visitable.Segment, + expected: functionSymbol.Parameters.Count, + actual: visitable.Parameters.Count); + + visitable.Parameters.Zip(functionSymbol.Parameters.ToArray()[(methodCall ? 1 : 0)..]) + .ToList().ForEach(pair => + { + var (expr, expected) = pair; + var actualType = expr.Accept(This); + if (!actualType.Equals(expected.Type)) + throw new WrongTypeOfArgument(expr.Segment, expected.Type, actualType); + }); + + Type undefined = "undefined"; + if (functionSymbol.Type.Equals(undefined)) + { + var declaration = _functionStorage.Get(functionSymbol); + functionReturnType = declaration.Accept(This); + } + + Type @void = "void"; + if (!functionReturnType.Equals(@void)) + visitable.HasReturnValue = true; + return functionReturnType; + } + + public Type Visit(FunctionDeclaration visitable) + { + var symbol = _symbolTables[visitable.Scope].FindSymbol(visitable.Name)!; + _functionStorage.RemoveIfPresent(symbol); + visitable.Statements.Accept(This); + + var returnStatements = visitable.ReturnStatements + .Select(x => new + { + Statement = x, + Type = x.Accept(This) + }); + Type undefined = "undefined"; + if (symbol.Type.Equals(undefined)) + { + var returnStatementTypes = returnStatements + .GroupBy(x => x.Type) + .Select(x => x.Key) + .ToList(); + if (returnStatementTypes.Count > 1) + throw new CannotDefineType(visitable.Segment); + symbol.DefineReturnType(returnStatementTypes.ElementAtOrDefault(0) ?? "void"); + } + else + { + var wrongReturn = returnStatements + .FirstOrDefault(x => !x.Type.Equals(symbol.Type)); + if (wrongReturn is not null) + throw new WrongReturnType( + wrongReturn.Statement.Segment, + expected: symbol.Type, + actual: wrongReturn.Type); + } + + Type @void = "void"; + var hasReturnStatement = visitable.HasReturnStatement(); + if (!symbol.Type.Equals(@void) && !hasReturnStatement) + throw new FunctionWithoutReturnStatement(visitable.Segment); + + return symbol.Type; + } + + public Type Visit(BlockStatement visitable) + { + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + return "undefined"; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SymbolTableInitializer.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SymbolTableInitializer.cs new file mode 100644 index 00000000..7b75c558 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SymbolTableInitializer.cs @@ -0,0 +1,61 @@ +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; +using HydraScript.Domain.IR.Impl; + +namespace HydraScript.Application.StaticAnalysis.Visitors; + +internal class SymbolTableInitializer : VisitorNoReturnBase, + IVisitor, + IVisitor, + IVisitor +{ + private readonly IStandardLibraryProvider _provider; + private readonly ISymbolTableStorage _symbolTables; + + public SymbolTableInitializer( + IStandardLibraryProvider provider, + ISymbolTableStorage symbolTables) + { + _provider = provider; + _symbolTables = symbolTables; + } + + public override VisitUnit Visit(IAbstractSyntaxTreeNode visitable) + { + visitable.InitScope(); + _symbolTables.Init(visitable.Scope, new SymbolTable()); + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + + return default; + } + + public VisitUnit Visit(ScriptBody visitable) + { + visitable.InitScope(new Scope()); + var symbolTable = _provider.GetStandardLibrary(); + _symbolTables.Init(visitable.Scope, symbolTable); + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + return default; + } + + public VisitUnit Visit(FunctionDeclaration visitable) + { + visitable.InitScope(scope: new Scope()); + _symbolTables.InitWithOpenScope(visitable.Scope); + visitable.Statements.Accept(This); + return default; + } + + public VisitUnit Visit(BlockStatement visitable) + { + visitable.InitScope(scope: new Scope()); + _symbolTables.InitWithOpenScope(visitable.Scope); + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + return default; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeBuilder.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeBuilder.cs new file mode 100644 index 00000000..71d1ae12 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeBuilder.cs @@ -0,0 +1,44 @@ +using HydraScript.Application.StaticAnalysis.Exceptions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.IR.Impl.Symbols; +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Visitors; + +internal class TypeBuilder : VisitorBase, + IVisitor, + IVisitor, + IVisitor, + IVisitor +{ + private readonly ISymbolTableStorage _symbolTables; + + public TypeBuilder(ISymbolTableStorage symbolTables) => + _symbolTables = symbolTables; + + public Type Visit(TypeIdentValue visitable) => + _symbolTables[visitable.Scope].FindSymbol(visitable.TypeId)?.Type ?? + throw new UnknownIdentifierReference(visitable.TypeId); + + public ArrayType Visit(ArrayTypeValue visitable) + { + visitable.TypeValue.Scope = visitable.Scope; + return new ArrayType(visitable.TypeValue.Accept(This)); + } + + public NullableType Visit(NullableTypeValue visitable) + { + visitable.TypeValue.Scope = visitable.Scope; + return new NullableType(visitable.TypeValue.Accept(This)); + } + + public ObjectType Visit(ObjectTypeValue visitable) => + new(visitable.Properties + .Select(x => + { + x.TypeValue.Scope = visitable.Scope; + return new PropertyType( + Id: x.Key, + x.TypeValue.Accept(This)); + })); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs new file mode 100644 index 00000000..8225e0fb --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs @@ -0,0 +1,58 @@ +using HydraScript.Application.StaticAnalysis.Exceptions; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.IR.Impl.Symbols; + +namespace HydraScript.Application.StaticAnalysis.Visitors; + +internal class TypeSystemLoader : VisitorNoReturnBase, + IVisitor, + IVisitor +{ + private readonly ITypeDeclarationsResolver _resolver; + private readonly IJavaScriptTypesProvider _provider; + private readonly ISymbolTableStorage _symbolTables; + + public TypeSystemLoader( + ITypeDeclarationsResolver resolver, + IJavaScriptTypesProvider provider, + ISymbolTableStorage symbolTables) + { + _resolver = resolver; + _provider = provider; + _symbolTables = symbolTables; + } + + public VisitUnit Visit(ScriptBody visitable) + { + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + _resolver.Resolve(); + return default; + } + + public override VisitUnit Visit(IAbstractSyntaxTreeNode visitable) + { + for (var i = 0; i < visitable.Count; i++) + visitable[i].Accept(This); + + return default; + } + + public VisitUnit Visit(TypeDeclaration visitable) + { + var symbolTable = _symbolTables[visitable.Scope]; + if (symbolTable.ContainsSymbol(visitable.TypeId) || + _provider.Contains(visitable.TypeId.Name)) + throw new DeclarationAlreadyExists(visitable.TypeId); + + symbolTable.AddSymbol( + new TypeSymbol( + visitable.TypeId.Name, + visitable.TypeId)); + + _resolver.Store(visitable); + return default; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs b/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs new file mode 100644 index 00000000..7f74e907 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/AddressedInstructions.cs @@ -0,0 +1,82 @@ +using System.Collections; +using HydraScript.Domain.BackEnd.Impl.Addresses; + +namespace HydraScript.Domain.BackEnd; + +public class AddressedInstructions : IEnumerable +{ + private readonly LinkedList _addresses = new(); + private readonly Dictionary> _addressToNode = new(); + private readonly Dictionary, IExecutableInstruction> _instructions = new(); + + public IExecutableInstruction this[IAddress address] + { + get => _instructions[_addressToNode[address]]; + private set => _instructions[_addressToNode[address]] = value; + } + + public IAddress Start => + _addresses.First?.Value!; + + public IAddress End => + _addresses.Last?.Value!; + + public void Add(IExecutableInstruction instruction, string? label = null) + { + IAddress newAddress = label is null + ? new HashAddress(seed: instruction.GetHashCode()) + : new Label(label); + instruction.Address = newAddress; + + AddWithAddress(instruction, newAddress); + } + + public void Replace(IExecutableInstruction old, IExecutableInstruction @new) + { + var address = old.Address; + @new.Address = address; + + this[address] = @new; + } + + private void AddWithAddress(IExecutableInstruction instruction, IAddress newAddress) + { + var last = _addresses.Last; + if (last is not null) + last.Value.Next = newAddress; + + var newNode = _addresses.AddLast(newAddress); + + _addressToNode.Add(newAddress, newNode); + _instructions.Add(newNode, instruction); + } + + public void AddRange(IEnumerable instructions) + { + foreach (var instruction in instructions) + AddWithAddress(instruction, instruction.Address); + } + + public void Remove(IExecutableInstruction instruction) + { + var address = instruction.Address; + var nodeToRemove = _addressToNode[address]; + + var prev = nodeToRemove.Previous; + if (prev is not null) + { + prev.Value.Next = nodeToRemove.Next?.Value!; + } + + _addressToNode.Remove(address); + _instructions.Remove(nodeToRemove); + _addresses.Remove(nodeToRemove); + } + + public IEnumerator GetEnumerator() => + _addresses.Select(address => this[address]) + .GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Call.cs b/src/Domain/HydraScript.Domain.BackEnd/Call.cs new file mode 100644 index 00000000..c3cc4f61 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Call.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.BackEnd.Impl.Addresses; + +namespace HydraScript.Domain.BackEnd; + +public record Call( + IAddress From, + FunctionInfo To, + string? Where = null) +{ + public override string ToString() => + $"{From}: {Where} => {To.Start}: {To.Id}"; +} + +public record FunctionInfo(string Id) +{ + public Label Start => new($"Start_{this}"); + public Label End => new($"End_{this}"); + + public override string ToString() => Id; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Frame.cs b/src/Domain/HydraScript.Domain.BackEnd/Frame.cs new file mode 100644 index 00000000..e3e5d963 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Frame.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Domain.BackEnd; + +public class Frame(IAddress returnAddress, Frame? parentFrame = null) +{ + private readonly Dictionary _variables = new(); + + public IAddress ReturnAddress { get; } = returnAddress; + + public object? this[string id] + { + get => _variables.TryGetValue(id, out var value) + ? value + : parentFrame?[id]; + set => _variables[id] = value; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj b/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj new file mode 100644 index 00000000..00c662bd --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/HydraScript.Domain.BackEnd.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + true + + + + false + + + diff --git a/src/Domain/HydraScript.Domain.BackEnd/IAddress.cs b/src/Domain/HydraScript.Domain.BackEnd/IAddress.cs new file mode 100644 index 00000000..27015987 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IAddress.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IAddress : IEquatable +{ + IAddress Next { get; set; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IExecutableInstruction.cs b/src/Domain/HydraScript.Domain.BackEnd/IExecutableInstruction.cs new file mode 100644 index 00000000..33af4ac3 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IExecutableInstruction.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IExecutableInstruction +{ + public IAddress Address { get; set; } + public IAddress Execute(IExecuteParams executeParams); + public bool End { get; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs b/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs new file mode 100644 index 00000000..347077da --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IExecuteParams.cs @@ -0,0 +1,9 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IExecuteParams +{ + public Stack CallStack { get; } + public Stack Frames { get; } + public Queue Arguments { get; } + public TextWriter Writer { get; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IValue.cs b/src/Domain/HydraScript.Domain.BackEnd/IValue.cs new file mode 100644 index 00000000..847ebd44 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IValue.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IValue : IEquatable +{ + object? Get(Frame? frame); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs b/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs new file mode 100644 index 00000000..e8263e32 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/IVirtualMachine.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.BackEnd; + +public interface IVirtualMachine +{ + public IExecuteParams ExecuteParams { get; } + public void Run(AddressedInstructions instructions); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/HashAddress.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/HashAddress.cs new file mode 100644 index 00000000..6b93034b --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/HashAddress.cs @@ -0,0 +1,28 @@ +namespace HydraScript.Domain.BackEnd.Impl.Addresses; + +public class HashAddress(int seed) : IAddress +{ + private readonly int _seed = seed; + + private readonly Guid _id = Guid.NewGuid(); + + public IAddress Next { get; set; } = default!; + + public bool Equals(IAddress? other) + { + if (other is HashAddress hashed) + return _seed == hashed._seed && _id == hashed._id; + + return false; + } + + public override int GetHashCode() + { + var i1 = _seed ^ 17; + var i2 = 31 * _seed + i1; + + return HashCode.Combine(i1, i2); + } + + public override string ToString() => "\t"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/Label.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/Label.cs new file mode 100644 index 00000000..0478a2c0 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Addresses/Label.cs @@ -0,0 +1,18 @@ +namespace HydraScript.Domain.BackEnd.Impl.Addresses; + +public class Label(string name) : IAddress +{ + public string Name { get; } = name; + + public IAddress Next { get; set; } = default!; + + public bool Equals(IAddress? other) => + other is Label label && + Name == label.Name; + + public override int GetHashCode() => + Name.GetHashCode(); + + public override string ToString() => + $"{Name}:\n\t"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs new file mode 100644 index 00000000..4af9255c --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/ExecuteParams.cs @@ -0,0 +1,9 @@ +namespace HydraScript.Domain.BackEnd.Impl; + +public class ExecuteParams(TextWriter textWriter) : IExecuteParams +{ + public Stack CallStack { get; } = new(); + public Stack Frames { get; } = new(); + public Queue Arguments { get; } = new(); + public TextWriter Writer { get; } = textWriter; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/BlockLabel.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/BlockLabel.cs new file mode 100644 index 00000000..e3b2d08e --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/BlockLabel.cs @@ -0,0 +1,46 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public abstract class BlockLabel : Instruction +{ + private readonly BlockPosition _blockPosition; + private readonly BlockType _blockType; + private readonly string _blockId; + + protected BlockLabel(BlockPosition blockPosition, BlockType blockType, string blockId) + { + _blockPosition = blockPosition; + _blockType = blockType; + _blockId = blockId; + } + + public override IAddress Execute(IExecuteParams executeParams) => + Address.Next; + + protected override string ToStringInternal() => + $"{_blockPosition}{_blockType} {_blockId}"; + + protected enum BlockPosition + { + Begin, + End + } +} + +public enum BlockType +{ + Function, + Loop, + Condition +} + +public class BeginBlock : BlockLabel +{ + public BeginBlock(BlockType blockType, string blockId) : + base(BlockPosition.Begin, blockType, blockId) { } +} + +public class EndBlock : BlockLabel +{ + public EndBlock(BlockType blockType, string blockId) : + base(BlockPosition.End, blockType, blockId) { } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Halt.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Halt.cs new file mode 100644 index 00000000..1d95af05 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Halt.cs @@ -0,0 +1,16 @@ +using HydraScript.Domain.BackEnd.Impl.Addresses; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class Halt : Instruction +{ + public override bool End => true; + + public override IAddress Execute(IExecuteParams executeParams) + { + executeParams.Frames.Pop(); + return new HashAddress(seed: 0); + } + + protected override string ToStringInternal() => "End"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Instruction.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Instruction.cs new file mode 100644 index 00000000..2d7c38d2 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Instruction.cs @@ -0,0 +1,27 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public abstract class Instruction : IExecutableInstruction +{ + private IAddress _address = default!; + + public IAddress Address + { + get => _address; + set + { + OnSetOfAddress(value); + _address = value; + } + } + + protected virtual void OnSetOfAddress(IAddress address) { } + + public abstract IAddress Execute(IExecuteParams executeParams); + + public virtual bool End => false; + + protected abstract string ToStringInternal(); + + public override string ToString() => + $"{Address}{ToStringInternal()}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PopParameter.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PopParameter.cs new file mode 100644 index 00000000..2212889d --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PopParameter.cs @@ -0,0 +1,14 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class PopParameter(string parameter) : Instruction +{ + public override IAddress Execute(IExecuteParams executeParams) + { + var argument = executeParams.Arguments.Dequeue(); + executeParams.Frames.Peek()[parameter] = argument; + return Address.Next; + } + + protected override string ToStringInternal() => + $"PopParameter {parameter}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Print.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Print.cs new file mode 100644 index 00000000..db11b1f0 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Print.cs @@ -0,0 +1,13 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class Print(IValue value) : Instruction +{ + public override IAddress Execute(IExecuteParams executeParams) + { + executeParams.Writer.WriteLine(value.Get(executeParams.Frames.Peek())); + return Address.Next; + } + + protected override string ToStringInternal() => + $"Print {value}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PushParameter.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PushParameter.cs new file mode 100644 index 00000000..3630ec1e --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/PushParameter.cs @@ -0,0 +1,14 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class PushParameter(IValue value) : Instruction +{ + public override IAddress Execute(IExecuteParams executeParams) + { + executeParams.Arguments.Enqueue( + value.Get(executeParams.Frames.Peek())); + return Address.Next; + } + + protected override string ToStringInternal() => + $"PushParameter {value}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/RemoveFromArray.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/RemoveFromArray.cs new file mode 100644 index 00000000..b8e7a5ee --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/RemoveFromArray.cs @@ -0,0 +1,17 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class RemoveFromArray(string id, IValue index) : Instruction +{ + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + if (frame[id] is List list) + { + list.RemoveAt(Convert.ToInt32(index.Get(frame))); + } + return Address.Next; + } + + protected override string ToStringInternal() => + $"RemoveFrom {id} at {index}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Return.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Return.cs new file mode 100644 index 00000000..a30abf38 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/Return.cs @@ -0,0 +1,19 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions; + +public class Return(IValue? value = null) : Instruction +{ + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Pop(); + var call = executeParams.CallStack.Pop(); + if (call.Where != null && value != null) + { + executeParams.Frames.Peek()[call.Where] = value.Get(frame); + } + + return frame.ReturnAddress; + } + + protected override string ToStringInternal() => + $"Return{(value != null ? $" {value}" : "")}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/AsString.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/AsString.cs new file mode 100644 index 00000000..85bf6d0d --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/AsString.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; + +public partial class AsString(IValue value) : Simple(value) +{ + private static readonly AsStringSerializationContext AsStringJsonContext = new(new JsonSerializerOptions + { + WriteIndented = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals + }); + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + frame[Left!] = JsonSerializer.Serialize( + value: Right.right!.Get(frame)!, + AsStringJsonContext.Object); + + return Address.Next; + } + + protected override string ToStringInternal() => + $"{Left} = {Right.right} as string"; + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(bool))] + [JsonSerializable(typeof(double))] + [JsonSerializable(typeof(string))] + private partial class AsStringSerializationContext : JsonSerializerContext; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/CallFunction.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/CallFunction.cs new file mode 100644 index 00000000..2d7c5240 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/CallFunction.cs @@ -0,0 +1,24 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; + +public class CallFunction( + FunctionInfo function, + bool hasReturnValue) : Simple(null, (null, null), "Call ") +{ + protected override void OnSetOfAddress(IAddress address) + { + if (hasReturnValue) + base.OnSetOfAddress(address); + } + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = new Frame(Address.Next, executeParams.Frames.Peek()); + executeParams.CallStack.Push(new Call(Address, function, Left)); + executeParams.Frames.Push(frame); + return function.Start; + } + + protected override string ToStringInternal() => Left == null + ? $"Call {function}" + : $"{Left} = Call {function}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateArray.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateArray.cs new file mode 100644 index 00000000..13f52c05 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateArray.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; + +public class CreateArray(string id, int size) : Simple(id) +{ + private readonly string _id = id; + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + frame[_id] = new object[size].ToList(); + return Address.Next; + } + + protected override string ToStringInternal() => + $"array {_id} = [{size}]"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateObject.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateObject.cs new file mode 100644 index 00000000..43cd2070 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Create/CreateObject.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; + +public class CreateObject(string id) : Simple(id) +{ + private readonly string _id = id; + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + frame[_id] = new Dictionary(); + return Address.Next; + } + + protected override string ToStringInternal() => + $"object {_id} = {{}}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/DotRead.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/DotRead.cs new file mode 100644 index 00000000..4ca55d39 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/DotRead.cs @@ -0,0 +1,22 @@ +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Values; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; + +public class DotRead(Name @object, IValue property) : Simple( + leftValue: @object, + binaryOperator: ".", + rightValue: property), IReadFromComplexData +{ + private readonly IValue _property = property; + + public string Property => (string)_property.Get(frame: null)!; + + public Simple ToAssignment(IValue value) => + new DotAssignment(@object.ToString(), _property, value); + + public IExecutableInstruction ToInstruction() => this; + + protected override string ToStringInternal() => + $"{Left} = {@object}.{_property}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IReadFromComplexData.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IReadFromComplexData.cs new file mode 100644 index 00000000..f5b6caf8 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IReadFromComplexData.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; + +public interface IReadFromComplexData +{ + Simple ToAssignment(IValue value); + + IExecutableInstruction ToInstruction(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IndexRead.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IndexRead.cs new file mode 100644 index 00000000..af34e7db --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Read/IndexRead.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Values; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; + +public class IndexRead(Name array, IValue index) : Simple( + leftValue: array, + binaryOperator: "[]", + rightValue: index), IReadFromComplexData +{ + private readonly IValue _index = index; + + public Simple ToAssignment(IValue value) => + new IndexAssignment(array.ToString(), _index, value); + + public IExecutableInstruction ToInstruction() => this; + + protected override string ToStringInternal() => + $"{Left} = {array}[{_index}]"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/DotAssignment.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/DotAssignment.cs new file mode 100644 index 00000000..da535e00 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/DotAssignment.cs @@ -0,0 +1,26 @@ +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; +using HydraScript.Domain.BackEnd.Impl.Values; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; + +public class DotAssignment(string @object, IValue property, IValue value) + : Simple(left: @object, (property, value), "."), IWriteToComplexData +{ + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + if (frame[Left!] is Dictionary obj) + { + var field = (string?)Right.left?.Get(frame) ?? string.Empty; + obj[field] = Right.right!.Get(frame)!; + } + + return Address.Next; + } + + public Simple ToSimple() => + new DotRead(new Name(Left!), Right.left!); + + protected override string ToStringInternal() => + $"{Left}.{Right.left} = {Right.right}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IWriteToComplexData.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IWriteToComplexData.cs new file mode 100644 index 00000000..0f35a250 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IWriteToComplexData.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; + +public interface IWriteToComplexData +{ + Simple ToSimple(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IndexAssignment.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IndexAssignment.cs new file mode 100644 index 00000000..84520971 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/ComplexData/Write/IndexAssignment.cs @@ -0,0 +1,26 @@ +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Read; +using HydraScript.Domain.BackEnd.Impl.Values; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; + +public class IndexAssignment(string array, IValue index, IValue value) + : Simple(left: array, right: (index, value), "[]"), IWriteToComplexData +{ + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + if (frame[Left!] is List list) + { + var index = Convert.ToInt32(Right.left!.Get(frame)); + list[index] = Right.right!.Get(frame)!; + } + + return Address.Next; + } + + public Simple ToSimple() => + new IndexRead(new Name(Left!), Right.left!); + + protected override string ToStringInternal() => + $"{Left}[{Right.left}] = {Right.right}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs new file mode 100644 index 00000000..0af2110e --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs @@ -0,0 +1,98 @@ +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; + +public class Simple : Instruction +{ + public string? Left { get; set; } + + protected readonly (IValue? left, IValue? right) Right; + private readonly string _operator = string.Empty; + + protected Simple(string? left) => Left = left; + + public Simple( + string? left, + (IValue? left, IValue? right) right, + string @operator) + { + Left = left; + Right = right; + _operator = @operator; + } + + public Simple(IValue value) : this( + left: null, + right: (null, value), + @operator: string.Empty) + { + } + + public Simple(string unaryOperator, IValue value) : this( + left: null, + right: (null, value), + @operator: unaryOperator) + { + } + + public Simple( + IValue leftValue, + string binaryOperator, + IValue rightValue) : + this( + left: null, + right: (leftValue, rightValue), + @operator: binaryOperator) + { + } + + protected override void OnSetOfAddress(IAddress address) => + Left ??= $"_t{unchecked((uint)address.GetHashCode())}"; + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + if (Right.left == null) + { + var value = Right.right!.Get(frame); + frame[Left!] = _operator switch + { + "-" => -Convert.ToDouble(value), + "!" => !Convert.ToBoolean(value), + "~" => ((List)value!).Count, + "" => value, + _ => throw new NotSupportedException($"_operator {_operator} is not supported") + }; + } + else + { + object? lValue = Right.left.Get(frame), rValue = Right.right!.Get(frame); + frame[Left!] = _operator switch + { + "+" when lValue is string => lValue.ToString() + rValue, + "+" => Convert.ToDouble(lValue) + Convert.ToDouble(rValue), + "-" => Convert.ToDouble(lValue) - Convert.ToDouble(rValue), + "*" => Convert.ToDouble(lValue) * Convert.ToDouble(rValue), + "/" => Convert.ToDouble(lValue) / Convert.ToDouble(rValue), + "%" => Convert.ToDouble(lValue) % Convert.ToDouble(rValue), + "||" => Convert.ToBoolean(lValue) || Convert.ToBoolean(rValue), + "&&" => Convert.ToBoolean(lValue) && Convert.ToBoolean(rValue), + "==" => Equals(lValue, rValue), + "!=" => !Equals(lValue, rValue), + ">" => Convert.ToDouble(lValue) > Convert.ToDouble(rValue), + ">=" => Convert.ToDouble(lValue) >= Convert.ToDouble(rValue), + "<" => Convert.ToDouble(lValue) < Convert.ToDouble(rValue), + "<=" => Convert.ToDouble(lValue) <= Convert.ToDouble(rValue), + "." => ((Dictionary)lValue!)[rValue!.ToString()!], + "[]" => ((List)lValue!)[Convert.ToInt32(rValue)], + "++" => ((List)lValue!).Concat((List)rValue!).ToList(), + _ => throw new NotSupportedException($"_operator {_operator} is not supported") + }; + } + + return Address.Next; + } + + protected override string ToStringInternal() => + Right.left == null + ? $"{Left} = {_operator}{Right.right}" + : $"{Left} = {Right.left} {_operator} {Right.right}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/Goto.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/Goto.cs new file mode 100644 index 00000000..8a78c0c1 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/Goto.cs @@ -0,0 +1,31 @@ +using HydraScript.Domain.BackEnd.Impl.Addresses; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; + +public class Goto : Instruction +{ + protected Label Jump = default!; + + public InsideStatementJumpType? JumpType { get; } + + public Goto(Label jump) => + this.Jump = jump; + + public Goto(InsideStatementJumpType jumpType) => + JumpType = jumpType; + + public override IAddress Execute(IExecuteParams executeParams) => + Jump; + + public void SetJump(Label newJump) => + Jump = newJump; + + protected override string ToStringInternal() => + $"Goto {Jump.Name}"; +} + +public enum InsideStatementJumpType +{ + Break, + Continue +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/IfNotGoto.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/IfNotGoto.cs new file mode 100644 index 00000000..304834e3 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithJump/IfNotGoto.cs @@ -0,0 +1,23 @@ +using HydraScript.Domain.BackEnd.Impl.Addresses; + +namespace HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; + +public class IfNotGoto : Goto +{ + private readonly IValue _test; + + public IfNotGoto(IValue test, Label jump) : + base(jump) => + _test = test; + + public override IAddress Execute(IExecuteParams executeParams) + { + var frame = executeParams.Frames.Peek(); + return !Convert.ToBoolean(_test.Get(frame)) + ? Jump + : Address.Next; + } + + protected override string ToStringInternal() => + $"IfNot {_test} Goto {Jump.Name}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Constant.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Constant.cs new file mode 100644 index 00000000..7d41e68f --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Constant.cs @@ -0,0 +1,19 @@ +namespace HydraScript.Domain.BackEnd.Impl.Values; + +public class Constant(object? value, string representation) : IValue +{ + private readonly object? _value = value; + + public Constant(object value) : + this(value, representation: value.ToString()!) + { + } + + public object? Get(Frame? frame) => _value; + + public override string ToString() => representation; + + public bool Equals(IValue? other) => + other is Constant that && + Equals(_value, that._value); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Name.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Name.cs new file mode 100644 index 00000000..0ce77de1 --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Values/Name.cs @@ -0,0 +1,14 @@ +namespace HydraScript.Domain.BackEnd.Impl.Values; + +public class Name(string id) : IValue +{ + private readonly string _id = id; + + public object? Get(Frame? frame) => frame![_id]; + + public override string ToString() => _id; + + public bool Equals(IValue? other) => + other is Name that && + _id == that._id; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/VirtualMachine.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/VirtualMachine.cs new file mode 100644 index 00000000..67a715ff --- /dev/null +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/VirtualMachine.cs @@ -0,0 +1,21 @@ +namespace HydraScript.Domain.BackEnd.Impl; + +public class VirtualMachine(TextWriter writer) : IVirtualMachine +{ + public IExecuteParams ExecuteParams { get; } = new ExecuteParams(writer); + + public void Run(AddressedInstructions instructions) + { + ExecuteParams.Frames.Push(new Frame(instructions.Start)); + + var address = instructions.Start; + while (!instructions[address].End) + { + var instruction = instructions[address]; + var jump = instruction.Execute(ExecuteParams); + address = jump; + } + + instructions[address].Execute(ExecuteParams); + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/GlobalUsings.cs b/src/Domain/HydraScript.Domain.FrontEnd/GlobalUsings.cs new file mode 100644 index 00000000..8a66e1a7 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/GlobalUsings.cs @@ -0,0 +1,4 @@ +// Global using directives + +global using Expression = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.Expression; +global using Visitor.NET; \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/HydraScript.Domain.FrontEnd.csproj b/src/Domain/HydraScript.Domain.FrontEnd/HydraScript.Domain.FrontEnd.csproj new file mode 100644 index 00000000..b7b4735b --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/HydraScript.Domain.FrontEnd.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + true + + + + + + + + diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IGeneratedRegexContainer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IGeneratedRegexContainer.cs new file mode 100644 index 00000000..4ab0599d --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IGeneratedRegexContainer.cs @@ -0,0 +1,8 @@ +using System.Text.RegularExpressions; + +namespace HydraScript.Domain.FrontEnd.Lexer; + +public interface IGeneratedRegexContainer +{ + public static abstract Regex GetRegex(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ILexer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ILexer.cs new file mode 100644 index 00000000..bb694326 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ILexer.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Domain.FrontEnd.Lexer; + +public interface ILexer +{ + public IStructure Structure { get; } + + public List GetTokens(string text); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IStructure.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IStructure.cs new file mode 100644 index 00000000..18217c1e --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/IStructure.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +namespace HydraScript.Domain.FrontEnd.Lexer; + +public interface IStructure : IEnumerable +{ + public Regex Regex { get; } + + public TokenType FindByTag(string tag); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITextCoordinateSystemComputer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITextCoordinateSystemComputer.cs new file mode 100644 index 00000000..b468922e --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITextCoordinateSystemComputer.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Lexer; + +public interface ITextCoordinateSystemComputer +{ + public IReadOnlyList GetLines(string text); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITokenTypesProvider.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITokenTypesProvider.cs new file mode 100644 index 00000000..ffca2d4e --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/ITokenTypesProvider.cs @@ -0,0 +1,8 @@ +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +namespace HydraScript.Domain.FrontEnd.Lexer; + +public interface ITokenTypesProvider +{ + IEnumerable GetTokenTypes(); +} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Impl/Lexer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/RegexLexer.cs similarity index 59% rename from Interpreter.Lib/FrontEnd/GetTokens/Impl/Lexer.cs rename to src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/RegexLexer.cs index 224749a7..14604bcb 100644 --- a/Interpreter.Lib/FrontEnd/GetTokens/Impl/Lexer.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/RegexLexer.cs @@ -1,35 +1,20 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; -using Interpreter.Lib.FrontEnd.GetTokens.Data; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; -namespace Interpreter.Lib.FrontEnd.GetTokens.Impl; +namespace HydraScript.Domain.FrontEnd.Lexer.Impl; -public class Lexer : ILexer, IEnumerable +public class RegexLexer(IStructure structure, ITextCoordinateSystemComputer computer) : ILexer, IEnumerable { - private readonly List _lines = new(); + private IReadOnlyList _lines = []; private string _text = ""; - public Structure Structure { get; } - - public Lexer(Structure structure) => - Structure = structure; + public IStructure Structure { get; } = structure; public List GetTokens(string text) { _text = text; - - _lines.Clear(); - if (!string.IsNullOrEmpty(text)) - { - var lineMatches = - new Regex(@"(?\n)").Matches(text[^1] == '\n' - ? text - : new string(text.Append('\n').ToArray())); - foreach (Match match in lineMatches) - _lines.Add(match.Groups["NEWLINE"].Index); - } + _lines = computer.GetLines(_text); return this.Where(t => !t.Type.CanIgnore()).ToList(); } @@ -58,7 +43,7 @@ public IEnumerator GetEnumerator() } } - yield return new Token(TokenTypeUtils.End); + yield return new EndToken(); } [ExcludeFromCodeCoverage] diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/Structure.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/Structure.cs new file mode 100644 index 00000000..f082bfff --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/Structure.cs @@ -0,0 +1,33 @@ +using System.Collections; +using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +namespace HydraScript.Domain.FrontEnd.Lexer.Impl; + +public class Structure(ITokenTypesProvider provider) : IStructure + where TContainer : IGeneratedRegexContainer +{ + private FrozenDictionary Types { get; } = provider.GetTokenTypes() + .Concat([new EndOfProgramType(), new ErrorType()]) + .ToFrozenDictionary(x => x.Tag); + + public Regex Regex { get; } = TContainer.GetRegex(); + + public TokenType FindByTag(string tag) => + Types[tag]; + + public override string ToString() => + new StringBuilder() + .AppendJoin('\n', this) + .ToString(); + + // ReSharper disable once NotDisposedResourceIsReturned + public IEnumerator GetEnumerator() => + Types.Values.AsEnumerable().GetEnumerator(); + + [ExcludeFromCodeCoverage] + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs new file mode 100644 index 00000000..5aa435dc --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs @@ -0,0 +1,31 @@ +using System.Buffers; + +namespace HydraScript.Domain.FrontEnd.Lexer.Impl; + +public class TextCoordinateSystemComputer : ITextCoordinateSystemComputer +{ + private readonly SearchValues _sv = SearchValues.Create(['\n']); + + public IReadOnlyList GetLines(string text) + { + var newText = text.EndsWith(Environment.NewLine) + ? text + : text + Environment.NewLine; + var textLength = newText.Length; + + LinkedList indices = []; + while (true) + { + var start = indices.Last != null ? indices.Last.Value + 1 : 0; + if (start == textLength) + break; + var textAsSpan = newText.AsSpan( + start, + length: textLength - start); + var index = textAsSpan.IndexOfAny(_sv); + indices.AddLast(start + index); + } + + return indices.ToList(); + } +} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/GetTokens/LexerException.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/LexerException.cs similarity index 73% rename from Interpreter.Lib/FrontEnd/GetTokens/LexerException.cs rename to src/Domain/HydraScript.Domain.FrontEnd/Lexer/LexerException.cs index c6ff2439..215d2e8a 100644 --- a/Interpreter.Lib/FrontEnd/GetTokens/LexerException.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/LexerException.cs @@ -1,8 +1,8 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; +using System.Diagnostics.CodeAnalysis; -namespace Interpreter.Lib.FrontEnd.GetTokens; +namespace HydraScript.Domain.FrontEnd.Lexer; -[Serializable] +[Serializable, ExcludeFromCodeCoverage] public class LexerException : Exception { public LexerException() { } diff --git a/Interpreter.Lib/FrontEnd/GetTokens/Data/Token.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Token.cs similarity index 68% rename from Interpreter.Lib/FrontEnd/GetTokens/Data/Token.cs rename to src/Domain/HydraScript.Domain.FrontEnd/Lexer/Token.cs index 0da227fd..b75eea2d 100644 --- a/Interpreter.Lib/FrontEnd/GetTokens/Data/Token.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Token.cs @@ -1,27 +1,27 @@ using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; -using Interpreter.Lib.FrontEnd.GetTokens.Data.TokenTypes; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; -namespace Interpreter.Lib.FrontEnd.GetTokens.Data; +namespace HydraScript.Domain.FrontEnd.Lexer; [ExcludeFromCodeCoverage] -public record Token(TokenType Type) +public partial record Token(TokenType Type, Segment Segment, string Value) { - public Segment Segment { get; } - - public string Value { get; } - - public Token(TokenType type, Segment segment, string value) : - this(type) => - (Segment, Value) = (segment, value); - public override string ToString() { - var displayValue = Value; - if (displayValue != null) displayValue = Regex.Replace(Value, "\"", "\\\""); + var displayValue = Quotes().Replace(Value, "\\\""); + if (Type.CanIgnore()) displayValue = ""; return $"{Type} {Segment}: {displayValue}"; } + + [GeneratedRegex("\"")] + private static partial Regex Quotes(); +} + +public record EndToken() : Token(new EndOfProgramType(), null!, null!) +{ + public override string ToString() => Type.Tag; } [ExcludeFromCodeCoverage] @@ -31,6 +31,9 @@ public record Segment(Coordinates Start, Coordinates End) public static Segment operator +(Segment left, Segment right) => new(left.Start, right.End); + + public static implicit operator string(Segment segment) => + segment.ToString(); } [ExcludeFromCodeCoverage] diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/EndOfProgramType.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/EndOfProgramType.cs new file mode 100644 index 00000000..864b8070 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/EndOfProgramType.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +internal record EndOfProgramType() : TokenType(EopTag) +{ + public const string EopTag = "EOP"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/ErrorType.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/ErrorType.cs new file mode 100644 index 00000000..93c9de7d --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/ErrorType.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +internal record ErrorType() : TokenType("ERROR") +{ + public override bool Error() => true; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/IgnorableType.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/IgnorableType.cs new file mode 100644 index 00000000..810583c9 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/IgnorableType.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +public record IgnorableType(string Tag) : TokenType(Tag) +{ + public override bool CanIgnore() => true; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/TokenType.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/TokenType.cs new file mode 100644 index 00000000..90a81151 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypes/TokenType.cs @@ -0,0 +1,10 @@ +namespace HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +public record TokenType(string Tag) +{ + public virtual bool CanIgnore() => false; + + public virtual bool Error() => false; + + public sealed override string ToString() => Tag; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypesJson.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypesJson.cs new file mode 100644 index 00000000..97197054 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/TokenTypesJson.cs @@ -0,0 +1,119 @@ +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Domain.FrontEnd.Lexer; + +public static class TokenTypesJson +{ + [StringSyntax(StringSyntaxAttribute.Json)] + public const string String = + """ + [ + { + "tag": "Comment", + "pattern": "[\/]{2}.*", + "priority": 0, + "canIgnore": true + }, + { + "tag": "Ident", + "pattern": "[a-zA-Z][a-zA-Z0-9]*", + "priority": 50 + }, + { + "tag": "IntegerLiteral", + "pattern": "[0-9]+", + "priority": 3 + }, + { + "tag": "FloatLiteral", + "pattern": "[0-9]+[.][0-9]+", + "priority": 2 + }, + { + "tag": "NullLiteral", + "pattern": "null", + "priority": 4 + }, + { + "tag": "BooleanLiteral", + "pattern": "true|false", + "priority": 5 + }, + { + "tag": "StringLiteral", + "pattern": "\\\"(\\\\.|[^\"\\\\])*\\\"", + "priority": 6 + }, + { + "tag": "Keyword", + "pattern": "let|const|function|if|else|while|break|continue|return|as|type", + "priority": 11 + }, + { + "tag": "Operator", + "pattern": "[+]{1,2}|[-]|[*]|[\/]|[%]|([!]|[=])[=]|([<]|[>])[=]?|[!]|[|]{2}|[&]{2}|[~]|[:]{2}", + "priority": 12 + }, + { + "tag": "Comma", + "pattern": "[,]", + "priority": 100 + }, + { + "tag": "Dot", + "pattern": "[.]", + "priority": 105 + }, + { + "tag": "LeftCurl", + "pattern": "[{]", + "priority": 101 + }, + { + "tag": "RightCurl", + "pattern": "[}]", + "priority": 102 + }, + { + "tag": "LeftParen", + "pattern": "[(]", + "priority": 103 + }, + { + "tag": "RightParen", + "pattern": "[)]", + "priority": 104 + }, + { + "tag": "LeftBracket", + "pattern": "[[]", + "priority": 107 + }, + { + "tag": "RightBracket", + "pattern": "[]]", + "priority": 109 + }, + { + "tag": "Assign", + "pattern": "[=]", + "priority": 99 + }, + { + "tag": "QuestionMark", + "pattern": "[?]", + "priority": 90 + }, + { + "tag": "Colon", + "pattern": "[:]", + "priority": 91 + }, + { + "tag": "SemiColon", + "pattern": "[;]", + "priority": 92 + } + ] + """; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTree.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTree.cs new file mode 100644 index 00000000..ce4f6a98 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTree.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Parser; + +public interface IAbstractSyntaxTree +{ + public IAbstractSyntaxTreeNode Root { get; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs new file mode 100644 index 00000000..ee59c67a --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs @@ -0,0 +1,11 @@ +namespace HydraScript.Domain.FrontEnd.Parser; + +public interface IAbstractSyntaxTreeNode : + IReadOnlyList, + IVisitable +{ + public IAbstractSyntaxTreeNode Parent { get; } + public Scope Scope { get; } + public void InitScope(Scope? scope = null); + public IReadOnlyList GetAllNodes(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/IParser.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IParser.cs new file mode 100644 index 00000000..03d6c36c --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IParser.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Domain.FrontEnd.Parser; + +public interface IParser +{ + public IAbstractSyntaxTree Parse(string text); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTree.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTree.cs new file mode 100644 index 00000000..d4825d53 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTree.cs @@ -0,0 +1,25 @@ +using System.Text; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast; + +internal class AbstractSyntaxTree(IAbstractSyntaxTreeNode root) : IAbstractSyntaxTree +{ + public IAbstractSyntaxTreeNode Root { get; } = root; + + public override string ToString() + { + var tree = new StringBuilder("digraph ast {\n"); + var nodes = Root.GetAllNodes(); + for (var i = 0; i < nodes.Count; i++) + { + var node = nodes[i]; + tree.Append('\t').Append(node).Append('\n'); + for (var j = 0; j < node.Count; j++) + { + var child = node[j]; + tree.Append($"\t{node.GetHashCode()}->{child.GetHashCode()}\n"); + } + } + return tree.Append("}\n").ToString(); + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs new file mode 100644 index 00000000..dbfa573c --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs @@ -0,0 +1,66 @@ +using System.Collections; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast; + +public abstract class AbstractSyntaxTreeNode : IAbstractSyntaxTreeNode +{ + public IAbstractSyntaxTreeNode Parent { get; set; } = default!; + + public Scope Scope { get; protected set; } = default!; + + /// Базовая стратегия - инициализация через родительский узел + /// Обязательно null + public virtual void InitScope(Scope? scope = null) + { + if (scope is not null) + throw new ArgumentException("'scope' must be null"); + Scope = Parent.Scope; + } + + public string Segment { get; init; } = string.Empty; + + protected virtual IReadOnlyList Children { get; } = []; + + public IEnumerator GetEnumerator() => + Children.ToList().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + public int Count => Children.Count; + + public IAbstractSyntaxTreeNode this[int index] => + Children[index]; + + public IReadOnlyList GetAllNodes() + { + List result = [this]; + for (var index = 0; index < Children.Count; index++) + result.AddRange(Children[index].GetAllNodes()); + + return result; + } + + public bool ChildOf() where T : IAbstractSyntaxTreeNode + { + var parent = Parent; + while (parent != default!) + { + if (parent is T) + { + return true; + } + + parent = parent.Parent; + } + + return false; + } + + public virtual TReturn Accept(IVisitor visitor) => + visitor.DefaultVisit; + + protected abstract string NodeRepresentation(); + public override string ToString() => + $"{GetHashCode()} [label=\"{NodeRepresentation()}\"]"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/AfterTypesAreLoadedDeclaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/AfterTypesAreLoadedDeclaration.cs new file mode 100644 index 00000000..11b6b0e7 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/AfterTypesAreLoadedDeclaration.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; + +public abstract class AfterTypesAreLoadedDeclaration : Declaration +{ + public abstract override TReturn Accept( + IVisitor visitor); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs new file mode 100644 index 00000000..3625aa0c --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs @@ -0,0 +1,58 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; + +[AutoVisitable] +public partial class FunctionDeclaration : AfterTypesAreLoadedDeclaration +{ + private readonly List _arguments; + + protected override IReadOnlyList Children => [Statements]; + + public IdentifierReference Name { get; } + public TypeValue ReturnTypeValue { get; } + public IReadOnlyList Arguments => _arguments; + + public BlockStatement Statements { get; } + public bool IsEmpty => Statements.Count == 0; + + public FunctionDeclaration( + IdentifierReference name, + TypeValue returnTypeValue, + List arguments, + BlockStatement blockStatement) + { + Name = name; + ReturnTypeValue = returnTypeValue; + _arguments = arguments; + + Statements = blockStatement; + Statements.Parent = this; + + ReturnStatements = Statements + .GetAllNodes() + .OfType() + .ToArray(); + } + + /// Стратегия "блока" - углубление скоупа + /// Новый скоуп + public override void InitScope(Scope? scope = null) + { + ArgumentNullException.ThrowIfNull(scope); + Scope = scope; + Scope.AddOpenScope(Parent.Scope); + + _arguments.ForEach(x => x.TypeValue.Scope = Parent.Scope); + ReturnTypeValue.Scope = Parent.Scope; + } + + public bool HasReturnStatement() => + ReturnStatements.Count > 0; + + public IReadOnlyCollection ReturnStatements { get; } + + protected override string NodeRepresentation() => + "function " + Name; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/LexicalDeclaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/LexicalDeclaration.cs new file mode 100644 index 00000000..bd0c65d0 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/LexicalDeclaration.cs @@ -0,0 +1,24 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; + +[AutoVisitable] +public partial class LexicalDeclaration(bool readOnly) : AfterTypesAreLoadedDeclaration +{ + private readonly List _assignments = []; + protected override IReadOnlyList Children => + _assignments; + + public bool ReadOnly { get; } = readOnly; + public IReadOnlyList Assignments => + _assignments; + + public void AddAssignment(AssignmentExpression assignment) + { + assignment.Parent = this; + _assignments.Add(assignment); + } + + protected override string NodeRepresentation() => + ReadOnly ? "const" : "let"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeDeclaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeDeclaration.cs new file mode 100644 index 00000000..e4c7cbfb --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeDeclaration.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +[AutoVisitable] +public partial class TypeDeclaration(IdentifierReference typeId, TypeValue typeValue) : Declaration +{ + public IdentifierReference TypeId { get; } = typeId; + public TypeValue TypeValue { get; } = typeValue; + + /// + public override void InitScope(Scope? scope = null) + { + base.InitScope(scope); + TypeValue.Scope = Scope; + } + + protected override string NodeRepresentation() => + $"type {TypeId.Name} = {TypeValue}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs new file mode 100644 index 00000000..084e1ff4 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs @@ -0,0 +1,43 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +public abstract record TypeValue : IVisitable +{ + public Scope Scope { get; set; } = default!; + public abstract TReturn Accept(IVisitor visitor); +} + +[AutoVisitable] +public partial record TypeIdentValue(IdentifierReference TypeId) : TypeValue +{ + public override string ToString() => TypeId; +} + +[AutoVisitable] +public partial record ArrayTypeValue(TypeValue TypeValue) : TypeValue +{ + public override string ToString() => $"{TypeValue}[]"; +} + +[AutoVisitable] +public partial record NullableTypeValue(TypeValue TypeValue) : TypeValue +{ + public override string ToString() => $"{TypeValue}?"; +} + +public record PropertyTypeValue( + string Key, + TypeValue TypeValue) +{ + public override string ToString() => + $"{Key}: {TypeValue}"; +} + +[AutoVisitable] +public partial record ObjectTypeValue( + IEnumerable Properties) : TypeValue +{ + public override string ToString() => + $"{{{string.Join(';', Properties)}}}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs new file mode 100644 index 00000000..bb7d9289 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs @@ -0,0 +1,29 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; + +public abstract class AccessExpression : Expression +{ + public AccessExpression? Next { get; private set; } + + public AccessExpression? Prev => + Parent as AccessExpression; + + public Guid ComputedTypeGuid { get; set; } = Guid.Empty; + + protected AccessExpression(AccessExpression? prev) + { + if (prev is not null) + { + Parent = prev; + prev.Next = this; + } + } + + public bool HasNext() => + Next is not null; + + public bool HasPrev() => + Prev is not null; + + public abstract override TReturn Accept( + IVisitor visitor); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs new file mode 100644 index 00000000..663c0555 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs @@ -0,0 +1,20 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; + +[AutoVisitable] +public partial class DotAccess : AccessExpression +{ + protected override IReadOnlyList Children => + HasNext() ? [Property, Next!] : [Property]; + + public IdentifierReference Property { get; } + + public DotAccess(IdentifierReference property, AccessExpression? prev = null) : base(prev) + { + Property = property; + Property.Parent = this; + } + + protected override string NodeRepresentation() => "."; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs new file mode 100644 index 00000000..633dd27c --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs @@ -0,0 +1,18 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; + +[AutoVisitable] +public partial class IndexAccess : AccessExpression +{ + protected override IReadOnlyList Children => + HasNext() ? [Index, Next!] : [Index]; + + public Expression Index { get; } + + public IndexAccess(Expression index, AccessExpression? prev = null) : base(prev) + { + Index = index; + Index.Parent = this; + } + + protected override string NodeRepresentation() => "[]"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs new file mode 100644 index 00000000..5a3e28ad --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs @@ -0,0 +1,38 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class AssignmentExpression : Expression +{ + protected override IReadOnlyList Children => + [Destination, Source]; + + public LeftHandSideExpression Destination { get; } + public Expression Source { get; } + public TypeValue? DestinationType { get; } + + public AssignmentExpression( + LeftHandSideExpression lhs, + Expression source, + TypeValue? destinationType = null) + { + Destination = lhs; + lhs.Parent = this; + + Source = source; + source.Parent = this; + + DestinationType = destinationType; + } + + /// + public override void InitScope(Scope? scope = null) + { + base.InitScope(scope); + if (DestinationType is not null) + DestinationType.Scope = Scope; + } + + protected override string NodeRepresentation() => "="; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs new file mode 100644 index 00000000..bad0b5cc --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs @@ -0,0 +1,25 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class BinaryExpression : Expression +{ + protected override IReadOnlyList Children => + [Left, Right]; + + public Expression Left { get; } + public string Operator { get; } + public Expression Right { get; } + + public BinaryExpression(Expression left, string @operator, Expression right) + { + Left = left; + Left.Parent = this; + + Operator = @operator; + + Right = right; + Right.Parent = this; + } + + protected override string NodeRepresentation() => Operator; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs new file mode 100644 index 00000000..41c98e39 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs @@ -0,0 +1,33 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class CallExpression : LeftHandSideExpression +{ + private readonly List _parameters; + + protected override IReadOnlyList Children => + [Member, .._parameters]; + + public MemberExpression Member { get; } + public IReadOnlyList Parameters => _parameters; + + public bool IsEmptyCall { get; set; } + public bool HasReturnValue { get; set; } + + public CallExpression(MemberExpression member, IEnumerable expressions) + { + Member = member; + Member.Parent = this; + + _parameters = new List(expressions); + _parameters.ForEach(expr => expr.Parent = this); + } + + public override IdentifierReference Id => Member.Id; + + public override bool Empty() => Member.Empty(); + + protected override string NodeRepresentation() => "()"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs new file mode 100644 index 00000000..1fa87282 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs @@ -0,0 +1,30 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class CastAsExpression : Expression +{ + protected override IReadOnlyList Children => + [Expression]; + + public Expression Expression { get; } + public TypeValue Cast { get; } + + public CastAsExpression(Expression expression, TypeValue cast) + { + Expression = expression; + Expression.Parent = this; + + Cast = cast; + } + + /// + public override void InitScope(Scope? scope = null) + { + base.InitScope(scope); + Cast.Scope = Scope; + } + + protected override string NodeRepresentation() => $"as {Cast}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs new file mode 100644 index 00000000..7c84dc78 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs @@ -0,0 +1,20 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; + +[AutoVisitable] +public partial class ArrayLiteral : ComplexLiteral +{ + private readonly List _expressions; + + protected override IReadOnlyList Children => + _expressions; + + public IReadOnlyList Expressions => _expressions; + + public ArrayLiteral(IEnumerable expressions) + { + _expressions = new List(expressions); + _expressions.ForEach(expr => expr.Parent = this); + } + + protected override string NodeRepresentation() => "[]"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ComplexLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ComplexLiteral.cs new file mode 100644 index 00000000..47f00aea --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ComplexLiteral.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; + +public abstract class ComplexLiteral : Expression +{ + public string Id => Parent is AssignmentExpression assignment + ? assignment.Destination.Id + : $"_t{GetHashCode()}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs new file mode 100644 index 00000000..f8d01ea6 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs @@ -0,0 +1,20 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; + +[AutoVisitable] +public partial class ObjectLiteral : ComplexLiteral +{ + private readonly List _properties; + + protected override IReadOnlyList Children => + _properties; + + public IReadOnlyList Properties => _properties; + + public ObjectLiteral(IEnumerable properties) + { + _properties = new List(properties); + _properties.ForEach(prop => prop.Parent = this); + } + + protected override string NodeRepresentation() => "{}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs new file mode 100644 index 00000000..1336aa36 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs @@ -0,0 +1,33 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; + +[AutoVisitable] +public partial class Property : Expression +{ + protected override IReadOnlyList Children => + [Id, Expression]; + + public IdentifierReference Id { get; } + public Expression Expression { get; } + + public ObjectLiteral Object => + (Parent as ObjectLiteral)!; + + public Property(IdentifierReference id, Expression expression) + { + Id = id; + Id.Parent = this; + + Expression = expression; + Expression.Parent = this; + } + + public void Deconstruct(out string id, out Expression expr) + { + id = Id.Name; + expr = Expression; + } + + protected override string NodeRepresentation() => ":"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs new file mode 100644 index 00000000..090511ad --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs @@ -0,0 +1,25 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class ConditionalExpression : Expression +{ + protected override IReadOnlyList Children => + [Test, Consequent, Alternate]; + + public Expression Test { get; } + public Expression Consequent { get; } + public Expression Alternate { get; } + + public ConditionalExpression(Expression test, Expression consequent, Expression alternate) + { + Test = test; + Consequent = consequent; + Alternate = alternate; + + Test.Parent = this; + Consequent.Parent = this; + Alternate.Parent = this; + } + + protected override string NodeRepresentation() => "?:"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs new file mode 100644 index 00000000..a3766257 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +public abstract class Expression : AbstractSyntaxTreeNode +{ + public abstract override TReturn Accept( + IVisitor visitor); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs new file mode 100644 index 00000000..f15d9497 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs @@ -0,0 +1,10 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +public abstract class LeftHandSideExpression : Expression +{ + public abstract IdentifierReference Id { get; } + + public abstract bool Empty(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs new file mode 100644 index 00000000..33efd8e8 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs @@ -0,0 +1,44 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class MemberExpression : LeftHandSideExpression +{ + private readonly IdentifierReference _identifierReference; + + protected override IReadOnlyList Children => + AccessChain is not null ? [Id, AccessChain] : [Id]; + + public AccessExpression? AccessChain { get; } + public AccessExpression? Tail { get; } + + public Guid ComputedIdTypeGuid { get; set; } = Guid.Empty; + + public MemberExpression(IdentifierReference identifierReference) + { + _identifierReference = identifierReference; + _identifierReference.Parent = this; + } + + public MemberExpression( + IdentifierReference identifierReference, + AccessExpression? accessChain, + AccessExpression? tail) : this(identifierReference) + { + AccessChain = accessChain; + if (AccessChain is not null) + { + AccessChain.Parent = this; + } + + Tail = tail; + } + + public override IdentifierReference Id => _identifierReference; + + public override bool Empty() => AccessChain is null; + + protected override string NodeRepresentation() => nameof(MemberExpression); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs new file mode 100644 index 00000000..8396b932 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs @@ -0,0 +1,15 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +public abstract class AbstractLiteral(TypeValue type) : PrimaryExpression +{ + public TypeValue Type { get; } = type; + + /// + public override void InitScope(Scope? scope = null) + { + base.InitScope(scope); + Type.Scope = Parent.Scope; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs new file mode 100644 index 00000000..407577f6 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs @@ -0,0 +1,15 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +[AutoVisitable] +public partial class IdentifierReference(string name) : PrimaryExpression +{ + public string Name { get; } = name; + + protected override string NodeRepresentation() => Name; + + public override ValueDto ToValueDto() => + ValueDto.NameDto(Name); + + public static implicit operator string(IdentifierReference identifierReference) => + identifierReference.Name; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs new file mode 100644 index 00000000..21392f46 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs @@ -0,0 +1,19 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +[AutoVisitable] +public partial class ImplicitLiteral(TypeValue type) : AbstractLiteral(type) +{ + public object? ComputedDefaultValue { private get; set; } + + protected override string NodeRepresentation() => + Type.ToString(); + + public override ValueDto ToValueDto() => + ValueDto.ConstantDto( + ComputedDefaultValue, + ComputedDefaultValue is null + ? "null" + : ComputedDefaultValue.ToString()!); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs new file mode 100644 index 00000000..1a1d17f2 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs @@ -0,0 +1,26 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +[AutoVisitable] +public partial class Literal : AbstractLiteral +{ + private readonly object? _value; + private readonly string _label; + + public Literal( + TypeValue type, + object? value, + string segment, + string? label = null) : base(type) + { + _label = (label ?? value?.ToString())!; + _value = value; + Segment = segment; + } + + protected override string NodeRepresentation() => _label; + + public override ValueDto ToValueDto() => + ValueDto.ConstantDto(_value, _label); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs new file mode 100644 index 00000000..b0b68107 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/PrimaryExpression.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +[AutoVisitable] +public abstract partial class PrimaryExpression : Expression +{ + public abstract ValueDto ToValueDto(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs new file mode 100644 index 00000000..bd310884 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs @@ -0,0 +1,20 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +public record ValueDto( + ValueDtoType Type, + string? Name, + object? Value, + string? Label) +{ + public static ValueDto NameDto(string name) => + new(ValueDtoType.Name, name, Value: null, Label: null); + + public static ValueDto ConstantDto(object? value, string label) => + new(ValueDtoType.Constant, Name: null, value, label); +}; + +public enum ValueDtoType +{ + Constant = 1, + Name = 2 +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs new file mode 100644 index 00000000..61f7222a --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs @@ -0,0 +1,21 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; + +[AutoVisitable] +public partial class UnaryExpression : Expression +{ + protected override IReadOnlyList Children => + [Expression]; + + public string Operator { get; } + public Expression Expression { get; } + + public UnaryExpression(string @operator, Expression expression) + { + Operator = @operator; + + Expression = expression; + Expression.Parent = this; + } + + protected override string NodeRepresentation() => Operator; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/ScriptBody.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/ScriptBody.cs new file mode 100644 index 00000000..8f1642b1 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/ScriptBody.cs @@ -0,0 +1,28 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; + +[AutoVisitable] +public partial class ScriptBody : AbstractSyntaxTreeNode +{ + private readonly List _statementList; + + protected override IReadOnlyList Children => + _statementList; + + public IReadOnlyList StatementList => _statementList; + + public ScriptBody(IEnumerable statementList) + { + _statementList = new List(statementList); + _statementList.ForEach(item => item.Parent = this); + } + + /// В корень дерева загружается стандартная библиотека + /// Скоуп std + public override void InitScope(Scope? scope = null) + { + ArgumentNullException.ThrowIfNull(scope); + Scope = scope; + } + + protected override string NodeRepresentation() => "Script"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/StatementListItem.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/StatementListItem.cs new file mode 100644 index 00000000..e48633da --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/StatementListItem.cs @@ -0,0 +1,10 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; + +public abstract class StatementListItem : + AbstractSyntaxTreeNode; + +public abstract class Statement : + StatementListItem; + +public abstract class Declaration : + StatementListItem; \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs new file mode 100644 index 00000000..e063c23f --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs @@ -0,0 +1,29 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class BlockStatement : Statement +{ + private readonly List _statementList; + + protected override IReadOnlyList Children => + _statementList; + + public IReadOnlyList StatementList => _statementList; + + public BlockStatement(IEnumerable statementList) + { + _statementList = new List(statementList); + _statementList.ForEach(item => item.Parent = this); + } + + /// Стратегия "блока" - углубление скоупа + /// Новый скоуп + public override void InitScope(Scope? scope = null) + { + ArgumentNullException.ThrowIfNull(scope); + Scope = scope; + Scope.AddOpenScope(Parent.Scope); + } + + protected override string NodeRepresentation() => "{}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ExpressionStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ExpressionStatement.cs new file mode 100644 index 00000000..cdd3759d --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ExpressionStatement.cs @@ -0,0 +1,18 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class ExpressionStatement : Statement +{ + protected override IReadOnlyList Children => + [Expression]; + + public Expression Expression { get; } + + public ExpressionStatement(Expression expression) + { + Expression = expression; + Expression.Parent = this; + } + + protected override string NodeRepresentation() => nameof(ExpressionStatement); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/IfStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/IfStatement.cs new file mode 100644 index 00000000..74b19482 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/IfStatement.cs @@ -0,0 +1,37 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class IfStatement : Statement +{ + protected override IReadOnlyList Children => + Else is not null ? [Test, Then, Else] : [Test, Then]; + + public Expression Test { get; } + public Statement Then { get; } + public Statement? Else { get; } + + public IfStatement(Expression test, Statement then, Statement? @else = null) + { + Test = test; + Test.Parent = this; + + Then = then; + Then.Parent = this; + + if (@else is not null) + { + Else = @else; + Else.Parent = this; + } + } + + public bool Empty() => this is + { + Then.Count: 0, + Else: null or { Count: 0 } + }; + + public bool HasElseBlock() => Else is { Count: > 0 }; + + protected override string NodeRepresentation() => "if"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/InsideStatementJump.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/InsideStatementJump.cs new file mode 100644 index 00000000..3ee4e38c --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/InsideStatementJump.cs @@ -0,0 +1,12 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class InsideStatementJump(string keyword) : Statement +{ + public const string Break = "break"; + public const string Continue = "continue"; + + public string Keyword { get; } = keyword; + + protected override string NodeRepresentation() => Keyword; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ReturnStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ReturnStatement.cs new file mode 100644 index 00000000..3b8e6485 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/ReturnStatement.cs @@ -0,0 +1,21 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class ReturnStatement : Statement +{ + protected override IReadOnlyList Children => + Expression is not null ? [Expression] : []; + + public Expression? Expression { get; } + + public ReturnStatement(Expression? expression = null) + { + Expression = expression; + if (Expression is not null) + { + Expression.Parent = this; + } + } + + protected override string NodeRepresentation() => "return"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/WhileStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/WhileStatement.cs new file mode 100644 index 00000000..2b052ffe --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/WhileStatement.cs @@ -0,0 +1,22 @@ +namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +[AutoVisitable] +public partial class WhileStatement : Statement +{ + protected override IReadOnlyList Children => + [Condition, Statement]; + + public Expression Condition { get; } + public Statement Statement { get; } + + public WhileStatement(Expression condition, Statement statement) + { + Condition = condition; + Condition.Parent = this; + + Statement = statement; + Statement.Parent = this; + } + + protected override string NodeRepresentation() => "while"; +} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/TopDownParse/Impl/TokensStream.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TokensStream.cs similarity index 85% rename from Interpreter.Lib/FrontEnd/TopDownParse/Impl/TokensStream.cs rename to src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TokensStream.cs index 1c232a29..a258c818 100644 --- a/Interpreter.Lib/FrontEnd/TopDownParse/Impl/TokensStream.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TokensStream.cs @@ -1,7 +1,7 @@ using System.Collections; -using Interpreter.Lib.FrontEnd.GetTokens.Data; +using HydraScript.Domain.FrontEnd.Lexer; -namespace Interpreter.Lib.FrontEnd.TopDownParse.Impl; +namespace HydraScript.Domain.FrontEnd.Parser.Impl; public class TokensStream : IEnumerator { diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs new file mode 100644 index 00000000..bd9aad35 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs @@ -0,0 +1,747 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl; + +public class TopDownParser : IParser +{ + private TokensStream _tokens = new List(); + private readonly ILexer _lexer; + + public TopDownParser(ILexer lexer) => + _lexer = lexer; + + public IAbstractSyntaxTree Parse(string text) + { + _tokens = _lexer.GetTokens(text); + + var root = Script(); + Expect(EndOfProgramType.EopTag); + return new AbstractSyntaxTree(root); + } + + private Token Expect(string expectedTag, string? expectedValue = null) + { + var current = _tokens.Current; + + if (!CurrentIs(expectedTag)) + throw new ParserException(_tokens.Current.Segment, expectedTag, _tokens.Current); + if (_tokens.Current.Value != (expectedValue ?? _tokens.Current.Value)) + throw new ParserException(_tokens.Current.Segment, expectedValue, _tokens.Current); + + _tokens.MoveNext(); + return current; + } + + private bool CurrentIs(string tag) => + _tokens.Current.Type == _lexer.Structure.FindByTag(tag); + + private bool CurrentIsLiteral() => + CurrentIs("NullLiteral") || + CurrentIs("IntegerLiteral") || + CurrentIs("FloatLiteral") || + CurrentIs("StringLiteral") || + CurrentIs("BooleanLiteral"); + + private bool CurrentIsKeyword(string keyword) => + CurrentIs("Keyword") && + _tokens.Current.Value == keyword; + + private bool CurrentIsOperator(string @operator) => + CurrentIs("Operator") && + _tokens.Current.Value == @operator; + + private ScriptBody Script() => + new(StatementList()); + + private IEnumerable StatementList() + { + var statementList = new List(); + while ( + CurrentIsKeyword("function") || CurrentIsKeyword("let") || CurrentIsKeyword("const") || + CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || + CurrentIsOperator("-") || CurrentIsOperator("!") || CurrentIsOperator("~") || + CurrentIs("LeftCurl") || CurrentIsKeyword("return") || CurrentIsKeyword("break") || + CurrentIsKeyword("continue") || CurrentIsKeyword("if") || CurrentIsKeyword("while") || + CurrentIsKeyword("type") + ) + { + statementList.Add(StatementListItem()); + } + + return statementList; + } + + private StatementListItem StatementListItem() + { + if (CurrentIsKeyword("function") || CurrentIsKeyword("let") || + CurrentIsKeyword("const") || CurrentIsKeyword("type")) + { + return Declaration(); + } + + return Statement(); + } + + private Statement Statement() + { + if (CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen") || CurrentIsOperator("-") || + CurrentIsOperator("!") || CurrentIsOperator("~")) + { + return ExpressionStatement(); + } + + if (CurrentIs("LeftCurl")) + { + return BlockStatement(); + } + + if (CurrentIsKeyword("return")) + { + return ReturnStatement(); + } + + if (CurrentIsKeyword("break")) + { + return new InsideStatementJump(InsideStatementJump.Break) + { + Segment = Expect("Keyword", "break").Segment + }; + } + + if (CurrentIsKeyword("continue")) + { + return new InsideStatementJump(InsideStatementJump.Continue) + { + Segment = Expect("Keyword", "continue").Segment + }; + } + + if (CurrentIsKeyword("if")) + { + return IfStatement(); + } + + if (CurrentIsKeyword("while")) + { + return WhileStatement(); + } + + return null!; + } + + private BlockStatement BlockStatement() + { + Expect("LeftCurl"); + var block = new BlockStatement(StatementList()); + Expect("RightCurl"); + + return block; + } + + private ExpressionStatement ExpressionStatement() + { + return new(Expression()); + } + + private ReturnStatement ReturnStatement() + { + var ret = Expect("Keyword", "return"); + if (CurrentIs("Ident") || CurrentIsLiteral() || CurrentIs("LeftParen")|| + CurrentIsOperator("-") || CurrentIsOperator("!") || CurrentIsOperator("~") || + CurrentIs("LeftCurl") || CurrentIs("LeftBracket")) + { + return new ReturnStatement(Expression()) { Segment = ret.Segment }; + } + + return new ReturnStatement { Segment = ret.Segment }; + } + + private IfStatement IfStatement() + { + var token = Expect("Keyword", "if"); + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + var then = Statement(); + if (CurrentIsKeyword("else")) + { + Expect("Keyword", "else"); + var @else = Statement(); + return new IfStatement(expr, then, @else) {Segment = token.Segment}; + } + + return new IfStatement(expr, then) {Segment = token.Segment}; + } + + private WhileStatement WhileStatement() + { + var token = Expect("Keyword", "while"); + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + var stmt = Statement(); + return new WhileStatement(expr, stmt) {Segment = token.Segment}; + } + + private TypeDeclaration TypeDeclaration() + { + var typeWord = Expect("Keyword", "type"); + var ident = Expect("Ident"); + Expect("Assign"); + var type = TypeValue(); + + var typeId = new IdentifierReference(name: ident.Value) + { Segment = ident.Segment }; + + return new TypeDeclaration(typeId, type) { Segment = typeWord.Segment + ident.Segment }; + } + + private TypeValue TypeValue() + { + if (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + var identType = new TypeIdentValue( + TypeId: new IdentifierReference(name: ident.Value) + { Segment = ident.Segment }); + + return WithSuffix(identType); + } + + if (CurrentIs("LeftCurl")) + { + Expect("LeftCurl"); + var propertyTypes = new List(); + while (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + Expect("Colon"); + var propType = TypeValue(); + propertyTypes.Add( + new PropertyTypeValue( + ident.Value, + propType)); + Expect("SemiColon"); + } + + Expect("RightCurl"); + + return WithSuffix(new ObjectTypeValue(propertyTypes)); + } + + return null!; + } + + private TypeValue WithSuffix(TypeValue baseType) + { + var type = baseType; + while (CurrentIs("LeftBracket") || CurrentIs("QuestionMark")) + { + if (CurrentIs("LeftBracket")) + { + Expect("LeftBracket"); + Expect("RightBracket"); + type = new ArrayTypeValue(type); + } + else if (CurrentIs("QuestionMark")) + { + Expect("QuestionMark"); + type = new NullableTypeValue(type); + } + } + + return type; + } + + private Declaration Declaration() + { + if (CurrentIsKeyword("function")) + { + return FunctionDeclaration(); + } + + if (CurrentIsKeyword("let") || CurrentIsKeyword("const")) + { + return LexicalDeclaration(); + } + + if (CurrentIsKeyword("type")) + { + return TypeDeclaration(); + } + + return null!; + } + + private FunctionDeclaration FunctionDeclaration() + { + Expect("Keyword", "function"); + var ident = Expect("Ident"); + + Expect("LeftParen"); + var args = new List(); + if (CurrentIs("Ident")) + { + var arg = Expect("Ident").Value; + Expect("Colon"); + var type = TypeValue(); + args.Add(new PropertyTypeValue(arg, type)); + } + + while (CurrentIs("Comma")) + { + Expect("Comma"); + var arg = Expect("Ident").Value; + Expect("Colon"); + var type = TypeValue(); + args.Add(new PropertyTypeValue(arg, type)); + } + + var rp = Expect("RightParen"); + + TypeValue returnType = new TypeIdentValue( + TypeId: new IdentifierReference(name: "undefined") + { Segment = rp.Segment }); + + if (CurrentIs("Colon")) + { + Expect("Colon"); + returnType = TypeValue(); + } + + var name = new IdentifierReference(ident.Value) { Segment = ident.Segment }; + return new FunctionDeclaration(name, returnType, args, BlockStatement()) + { Segment = ident.Segment }; + } + + private LexicalDeclaration LexicalDeclaration() + { + var readOnly = CurrentIsKeyword("const"); + Expect("Keyword", readOnly ? "const" : "let"); + var declaration = new LexicalDeclaration(readOnly); + + AddToDeclaration(declaration); + + while (CurrentIs("Comma")) + { + Expect("Comma"); + AddToDeclaration(declaration); + } + + return declaration; + } + + private void AddToDeclaration(LexicalDeclaration declaration) + { + var ident = Expect("Ident"); + var identRef = new IdentifierReference(ident.Value) { Segment = ident.Segment }; + var assignment = new AssignmentExpression( + new MemberExpression(identRef), + new ImplicitLiteral( + new TypeIdentValue( + new IdentifierReference("undefined")))) + { Segment = ident.Segment }; + + if (CurrentIs("Assign")) + { + var assignSegment = Expect("Assign").Segment; + var expression = Expression(); + assignment = new AssignmentExpression( + new MemberExpression(identRef), expression + ) { Segment = assignSegment }; + } + else if (CurrentIs("Colon")) + { + Expect("Colon"); + var type = TypeValue(); + if (CurrentIs("Assign")) + { + var assignSegment = Expect("Assign").Segment; + var expression = Expression(); + assignment = new AssignmentExpression( + new MemberExpression(identRef), + expression, type + ) { Segment = assignSegment }; + } + else + { + var expression = new ImplicitLiteral(type); + assignment = new AssignmentExpression( + lhs: new MemberExpression(identRef), + expression, + type); + } + } + declaration.AddAssignment(assignment); + } + + private Expression Expression() + { + var expr = CastExpression(); + if (expr is LeftHandSideExpression lhs && CurrentIs("Assign")) + { + var assign = Expect("Assign"); + return new AssignmentExpression(lhs, Expression()) + {Segment = assign.Segment}; + } + return expr; + } + + private Expression CallExpression() + { + var member = MemberExpression(); + if (CurrentIs("LeftParen")) + { + var lp = Expect("LeftParen"); + var expressions = new List(); + if (CurrentIs("Ident") || CurrentIsLiteral() || + CurrentIs("LeftParen") || CurrentIsOperator("-") || + CurrentIsOperator("!") || CurrentIsOperator("~") || + CurrentIs("LeftCurl") || CurrentIs("LeftBracket")) + { + expressions.Add(Expression()); + } + + while (CurrentIs("Comma")) + { + Expect("Comma"); + expressions.Add(Expression()); + } + + Expect("RightParen"); + return new CallExpression((member as MemberExpression)!, expressions) + { Segment = lp.Segment }; + } + + return member; + } + + private Expression MemberExpression() + { + var primary = PrimaryExpression(); + + if (!CurrentIs("LeftBracket") && !CurrentIs("Dot") && + !CurrentIs("Assign") && !CurrentIs("LeftParen")) + return primary; + + var identRef = primary as IdentifierReference; + var accessChain = new List(); + while (CurrentIs("LeftBracket") || CurrentIs("Dot")) + { + Token access; + if (CurrentIs("LeftBracket")) + { + access = Expect("LeftBracket"); + var lb = access.Segment; + var expr = Expression(); + var rb = Expect("RightBracket").Segment; + accessChain.Add( + new IndexAccess(expr, accessChain.LastOrDefault()) {Segment = lb + rb} + ); + } + else if (CurrentIs("Dot")) + { + access = Expect("Dot"); + var identToken = Expect("Ident"); + var idRef = new IdentifierReference(identToken.Value) + { Segment = identToken.Segment }; + accessChain.Add( + new DotAccess(idRef, accessChain.LastOrDefault()) {Segment = access.Segment} + ); + } + } + + return new MemberExpression( + identRef!, + accessChain.FirstOrDefault(), + tail: accessChain.LastOrDefault()); + } + + private Expression CastExpression() + { + var cond = ConditionalExpression(); + if (CurrentIsKeyword("as")) + { + var asKeyword = Expect("Keyword", "as"); + var type = TypeValue(); + return new CastAsExpression(cond, type) {Segment = asKeyword.Segment}; + } + + return cond; + } + + private Expression ConditionalExpression() + { + var test = OrExpression(); + if (CurrentIs("QuestionMark")) + { + Expect("QuestionMark"); + var consequent = Expression(); + Expect("Colon"); + var alternate = Expression(); + return new ConditionalExpression(test, consequent, alternate) + { + Segment = consequent.Segment + alternate.Segment + }; + } + + return test; + } + + private Expression OrExpression() + { + var left = AndExpression(); + while (CurrentIsOperator("||")) + { + var op = Expect("Operator"); + var right = AndExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression AndExpression() + { + var left = EqualityExpression(); + while (CurrentIsOperator("&&")) + { + var op = Expect("Operator"); + var right = EqualityExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression EqualityExpression() + { + var left = RelationExpression(); + while (CurrentIsOperator("==") || CurrentIsOperator("!=")) + { + var op = Expect("Operator"); + var right = RelationExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression RelationExpression() + { + var left = AdditiveExpression(); + while (CurrentIsOperator(">") || CurrentIsOperator("<") || CurrentIsOperator(">=") || + CurrentIsOperator("<=")) + { + var op = Expect("Operator"); + var right = AdditiveExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression AdditiveExpression() + { + var left = MultiplicativeExpression(); + while (CurrentIsOperator("+") || CurrentIsOperator("-")) + { + var op = Expect("Operator"); + var right = MultiplicativeExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression MultiplicativeExpression() + { + var left = UnaryExpression(); + while (CurrentIsOperator("*") || CurrentIsOperator("/") || CurrentIsOperator("%") + || CurrentIsOperator("++") || CurrentIsOperator("::")) + { + var op = Expect("Operator"); + var right = UnaryExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + private Expression UnaryExpression() + { + if (CurrentIsOperator("-") || CurrentIsOperator("!") || CurrentIsOperator("~")) + { + var op = Expect("Operator"); + return new UnaryExpression(op.Value, UnaryExpression()) + { + Segment = op.Segment + }; + } + + return LeftHandSideExpression(); + } + + private Expression LeftHandSideExpression() + { + return CallExpression(); + } + + private Expression PrimaryExpression() + { + if (CurrentIs("LeftParen")) + { + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + return expr; + } + + if (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + var id = new IdentifierReference(ident.Value) + { + Segment = ident.Segment + }; + + return id; + } + + if (CurrentIsLiteral()) + { + return Literal(); + } + + if (CurrentIs("LeftCurl")) + { + return ObjectLiteral(); + } + + if (CurrentIs("LeftBracket")) + { + return ArrayLiteral(); + } + + return null!; + } + + private Literal Literal() + { + var segment = _tokens.Current.Segment; + if (CurrentIs("StringLiteral")) + { + var str = Expect("StringLiteral"); + return new Literal( + new TypeIdentValue( + TypeId: new IdentifierReference(name: "string") + {Segment = str.Segment}), + value: Regex.Unescape(str.Value.Trim('"')), + segment, + label: str.Value + .Replace(@"\", @"\\") + .Replace(@"""", @"\""")); + } + + return _tokens.Current.Type.Tag switch + { + "NullLiteral" => new Literal( + new TypeIdentValue( + TypeId: new IdentifierReference(name: "null") + { Segment = _tokens.Current.Segment }), + Expect("NullLiteral").Value == "null" ? null : string.Empty, + segment, + label: "null"), + "IntegerLiteral" => new Literal( + new TypeIdentValue( + TypeId: new IdentifierReference(name: "number") + { Segment = _tokens.Current.Segment }), + value: double.Parse(Expect("IntegerLiteral").Value), + segment), + "FloatLiteral" => new Literal( + new TypeIdentValue( + TypeId: new IdentifierReference(name: "number") + { Segment = _tokens.Current.Segment }), + value: double.Parse( + Expect("FloatLiteral").Value, + CultureInfo.InvariantCulture), + segment), + "BooleanLiteral" => new Literal( + new TypeIdentValue( + TypeId: new IdentifierReference(name: "boolean") + { Segment = _tokens.Current.Segment }), + value: bool.Parse(Expect("BooleanLiteral").Value), + segment), + _ => throw new ParserException("There are no more supported literals") + }; + } + + private ObjectLiteral ObjectLiteral() + { + Expect("LeftCurl"); + var properties = new List(); + while (CurrentIs("Ident")) + { + var idToken = Expect("Ident"); + var id = new IdentifierReference(idToken.Value) + { Segment = idToken.Segment }; + + Expect("Colon"); + var expr = Expression(); + properties.Add(new Property(id, expr)); + + Expect("SemiColon"); + } + Expect("RightCurl"); + return new ObjectLiteral(properties); + } + + private ArrayLiteral ArrayLiteral() + { + var lb = Expect("LeftBracket").Segment; + var expressions = new List(); + while (CurrentIs("Ident") || CurrentIsLiteral() || + CurrentIs("LeftParen") || CurrentIsOperator("-") || + CurrentIsOperator("!") || CurrentIsOperator("~") || + CurrentIs("LeftCurl") || CurrentIs("LeftBracket")) + { + expressions.Add(Expression()); + if (!CurrentIs("RightBracket")) + { + Expect("Comma"); + } + } + var rb = Expect("RightBracket").Segment; + return new ArrayLiteral(expressions) {Segment = lb + rb}; + } +} \ No newline at end of file diff --git a/Interpreter.Lib/FrontEnd/TopDownParse/ParserException.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs similarity index 50% rename from Interpreter.Lib/FrontEnd/TopDownParse/ParserException.cs rename to src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs index 4d9ba3b6..995dbf2d 100644 --- a/Interpreter.Lib/FrontEnd/TopDownParse/ParserException.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs @@ -1,17 +1,18 @@ -using Interpreter.Lib.FrontEnd.GetTokens.Data; +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.FrontEnd.Lexer; -namespace Interpreter.Lib.FrontEnd.TopDownParse; +namespace HydraScript.Domain.FrontEnd.Parser; -[Serializable] +[Serializable, ExcludeFromCodeCoverage] public class ParserException : Exception { public ParserException() { } - protected ParserException(string message) : base(message) { } + public ParserException(string message) : base(message) { } protected ParserException(string message, Exception inner) : base(message, inner) { } - public ParserException(Segment segment, string expected, Token actual) : + public ParserException(Segment segment, string? expected, Token actual) : base($"Wrong syntax: {segment} expected {expected}; actual = ({actual.Type.Tag}, {actual.Value})") { } diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs new file mode 100644 index 00000000..86e00d17 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs @@ -0,0 +1,12 @@ +namespace HydraScript.Domain.FrontEnd.Parser; + +public record Scope +{ + public Guid Id { get; } = Guid.NewGuid(); + public Scope? OpenScope { get; private set; } + + public void AddOpenScope(Scope scope) => + OpenScope = scope; + + public override string ToString() => Id.ToString(); +} \ No newline at end of file diff --git a/Interpreter/grammar.txt b/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt similarity index 69% rename from Interpreter/grammar.txt rename to src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt index a6eec2fc..0546398d 100644 --- a/Interpreter/grammar.txt +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt @@ -10,31 +10,37 @@ Statement -> BlockStatement ContinueStatement BreakStatement ReturnStatement - TypeStatement Declaration -> LexicalDeclaration FunctionDeclaration + TypeDeclaration BlockStatement -> '{' StatementList '}' ExpressionStatement -> Expression -Expression -> AssignExpression -AssignExpression -> LeftHandSideExpression ('=' AssignExpression)? -LeftHandSideExpression -> CastExpression +Expression -> CastExpression + AssignmentExpression + +CastExpression -> ConditionalExpression 'as' 'string' + +AssignmentExpression -> LeftHandSideExpression '=' Expression + +LeftHandSideExpression -> MemberExpression CallExpression -CallExpression -> MemberExpression Arguments* -Arguments -> '(' ArgumentsList? ')' -ArgumentsList -> AssignExpression (',' AssignExpression)* -MemberExpression -> "Ident" ('[' Expression ']' | '.' "Ident" )* -CastExpression -> ConditionalExpression 'as' TypeIdentifier -ConditionalExpression -> OrExpression ('?' AssignExpression ':' AssignExpression)? +CallExpression -> MemberExpression Arguments (Arguments | '[' Expression ']' | '.' 'Ident')* +MemberExpression -> "Ident" ('[' Expression ']' | '.' 'Ident')* + +Arguments -> '(' (Expression ',')* ')' + +ConditionalExpression -> OrExpression ('?' Expression ':' Expression)? OrExpression -> AndExpression ('||' AndExpression)* AndExpression -> EqExpression ('&&' EqExpression)* EqExpression -> RelExpression (('=='|'!=') RelExpression)* RelExpression -> AddExpression (('<'|'>'|'<='|'>=') AddExpression)* AddExpression -> MulExpression (('+'|'-') MulExpression)* -MulExpression -> UnaryExpression (('*'|'/'|'%') UnaryExpression)* -UnaryExpression -> PrimaryExpression | ('-'|'!') UnaryExpression +MulExpression -> UnaryExpression (('*'|'/'|'%'|'++'|'::') UnaryExpression)* +UnaryExpression -> LeftHandSideExpression | ('-'|'!'|'~') UnaryExpression + PrimaryExpression -> "Ident" | Literal | '(' Expression ')' | ObjectLiteral | ArrayLiteral Literal -> "NullLiteral" "IntegerLiteral" @@ -42,12 +48,9 @@ Literal -> "NullLiteral" "StringLiteral" "BooleanLiteral" ObjectLiteral -> '{' PropertyDefinitionList '}' -PropertyDefinitionList -> (Property ';')* -Property -> FieldProperty - MethodProperty +PropertyDefinitionList -> (FieldProperty ';')* FieldProperty -> "Ident" ':' Expression -MethodProperty -> "Ident" '=>' MethodDeclaration -MethodDeclaration -> '(' FunctionParameters? ')' Type? BlockStatement + ArrayLiteral -> '[' ElementList ']' ElementList -> (Expression ',')* @@ -61,15 +64,13 @@ BreakStatement -> 'break' ReturnStatement -> 'return' Expression? -TypeStatement -> 'type' "Ident" = TypeValue +TypeDeclaration -> 'type' "Ident" = TypeValue TypeValue -> TypeValueBase TypeValueSuffix* TypeValueBase -> "Ident" - FunctionTypeBase ObjectTypeBase ObjectTypeBase -> '{' PropertyTypeList '}' PropertyTypeList -> (PropertyType ';')* PropertyType -> "Ident" ':' TypeValue -FunctionTypeBase -> '(' ArgTypeList ')' '=>' TypeValue ArgTypeList -> (TypeValue ',')* TypeValueSuffix -> '['']' '?' diff --git a/src/Domain/HydraScript.Domain.IR/GlobalUsings.cs b/src/Domain/HydraScript.Domain.IR/GlobalUsings.cs new file mode 100644 index 00000000..4f90012f --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/GlobalUsings.cs @@ -0,0 +1,3 @@ +// Global using directives + +global using Type = HydraScript.Domain.IR.Types.Type; \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj b/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj new file mode 100644 index 00000000..7faaaf6b --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + true + + + diff --git a/src/Domain/HydraScript.Domain.IR/ISymbol.cs b/src/Domain/HydraScript.Domain.IR/ISymbol.cs new file mode 100644 index 00000000..9af22f65 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/ISymbol.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.IR; + +public interface ISymbol +{ + public string Id { get; } + public Type Type { get; } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/ISymbolTable.cs b/src/Domain/HydraScript.Domain.IR/ISymbolTable.cs new file mode 100644 index 00000000..bc5d5811 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/ISymbolTable.cs @@ -0,0 +1,34 @@ +namespace HydraScript.Domain.IR; + +public interface ISymbolTable +{ + /// + /// Добавление области видимости + /// + /// Доступная область видимости + public void AddOpenScope(ISymbolTable table); + + /// + /// Символы доступные в области видимости таблицы + /// + public IEnumerable GetAvailableSymbols(); + + /// + /// Добавление собственного символа в область видимости + /// + /// Собственный символ + public void AddSymbol(ISymbol symbol); + + /// + /// Поиск эффективного символа + /// + /// Идентификатор символа + public TSymbol? FindSymbol(string id) + where TSymbol : class, ISymbol; + + /// + /// Проверка наличия собственного символа + /// + /// Идентификатор символа + public bool ContainsSymbol(string id); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/SymbolTable.cs b/src/Domain/HydraScript.Domain.IR/Impl/SymbolTable.cs new file mode 100644 index 00000000..9dfdfab4 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/SymbolTable.cs @@ -0,0 +1,32 @@ +namespace HydraScript.Domain.IR.Impl; + +public class SymbolTable : ISymbolTable +{ + private readonly Dictionary _symbols = []; + private ISymbolTable? _openScope; + + /// + public void AddOpenScope(ISymbolTable table) => _openScope = table; + + /// + public IEnumerable GetAvailableSymbols() => + _symbols.Values.Concat(_openScope?.GetAvailableSymbols() ?? []); + + /// + public void AddSymbol(ISymbol symbol) => + _symbols[symbol.Id] = symbol; + + /// + public TSymbol? FindSymbol(string id) + where TSymbol : class, ISymbol + { + var hasInsideTheScope = _symbols.TryGetValue(id, out var symbol); + return !hasInsideTheScope + ? _openScope?.FindSymbol(id) + : symbol as TSymbol; + } + + /// + public bool ContainsSymbol(string id) => + _symbols.ContainsKey(id); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/Symbols/FunctionSymbol.cs b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/FunctionSymbol.cs new file mode 100644 index 00000000..9b452870 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/FunctionSymbol.cs @@ -0,0 +1,26 @@ +using System.Text; + +namespace HydraScript.Domain.IR.Impl.Symbols; + +public class FunctionSymbol( + string id, + IEnumerable parameters, + Type type, + bool isEmpty) : Symbol(id, type) +{ + private Type _returnType = type; + /// Тип возврата функции + public override Type Type => _returnType; + + public IReadOnlyList Parameters { get; } = new List(parameters); + public bool IsEmpty { get; } = isEmpty; + + public void DefineReturnType(Type returnType) => + _returnType = returnType; + + public override string ToString() => + new StringBuilder($"function {Id}(") + .AppendJoin(',', Parameters) + .Append($") => {Type}") + .ToString(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/Symbols/ObjectSymbol.cs b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/ObjectSymbol.cs new file mode 100644 index 00000000..5a4bcf9b --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/ObjectSymbol.cs @@ -0,0 +1,11 @@ +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Domain.IR.Impl.Symbols; + +public class ObjectSymbol( + string id, + ObjectType objectType, + bool readOnly = false) : VariableSymbol(id, objectType, readOnly) +{ + public override ObjectType Type { get; } = objectType; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/Symbols/Symbol.cs b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/Symbol.cs new file mode 100644 index 00000000..cf793acc --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/Symbol.cs @@ -0,0 +1,7 @@ +namespace HydraScript.Domain.IR.Impl.Symbols; + +public abstract class Symbol(string id, Type type) : ISymbol +{ + public virtual string Id { get; } = id; + public virtual Type Type { get; } = type; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/Symbols/TypeSymbol.cs b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/TypeSymbol.cs new file mode 100644 index 00000000..c3fda2a8 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/TypeSymbol.cs @@ -0,0 +1,15 @@ +namespace HydraScript.Domain.IR.Impl.Symbols; + +public class TypeSymbol(Type type, string? id = null) : + Symbol(id ?? type.ToString(), type) +{ + public override bool Equals(object? obj) => + obj is TypeSymbol typeSymbol && + Id == typeSymbol.Id && Type.Equals(typeSymbol.Type); + + public override int GetHashCode() => + HashCode.Combine(Id, Type); + + public override string ToString() => + $"type {Id} = {Type}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Impl/Symbols/VariableSymbol.cs b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/VariableSymbol.cs new file mode 100644 index 00000000..dc93ad5d --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Impl/Symbols/VariableSymbol.cs @@ -0,0 +1,12 @@ +namespace HydraScript.Domain.IR.Impl.Symbols; + +public class VariableSymbol( + string id, + Type type, + bool readOnly = false) : Symbol(id, type) +{ + public bool ReadOnly { get; } = readOnly; + + public override string ToString() => + $"{(ReadOnly ? "const " : "")}{Id}: {Type}"; +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Any.cs b/src/Domain/HydraScript.Domain.IR/Types/Any.cs new file mode 100644 index 00000000..7ff16012 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Any.cs @@ -0,0 +1,8 @@ +namespace HydraScript.Domain.IR.Types; + +public class Any() : Type("any") +{ + public override bool Equals(object? obj) => true; + + public override int GetHashCode() => "any".GetHashCode(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs b/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs new file mode 100644 index 00000000..791b9c9f --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs @@ -0,0 +1,28 @@ +namespace HydraScript.Domain.IR.Types; + +public class ArrayType(Type type) : Type($"{type}[]") +{ + public Type Type { get; private set; } = type; + + public override void ResolveReference( + Type reference, + string refId, + ISet? visited = null) + { + if (Type == refId) + Type = reference; + else + Type.ResolveReference(reference, refId, visited); + } + + public override bool Equals(object? obj) + { + if (obj is ArrayType that) + return Equals(Type, that.Type); + return obj is Any; + } + + public override int GetHashCode() => + // ReSharper disable once NonReadonlyMemberInGetHashCode + Type.GetHashCode(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/NullType.cs b/src/Domain/HydraScript.Domain.IR/Types/NullType.cs new file mode 100644 index 00000000..4dcccefd --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/NullType.cs @@ -0,0 +1,10 @@ +namespace HydraScript.Domain.IR.Types; + +public class NullType() : Type("null") +{ + public override bool Equals(object? obj) => + obj is NullableType or NullType or Any; + + public override int GetHashCode() => + "null".GetHashCode(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs b/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs new file mode 100644 index 00000000..e88383d7 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs @@ -0,0 +1,37 @@ +namespace HydraScript.Domain.IR.Types; + +public class NullableType : Type +{ + public Type Type { get; private set; } = default!; + + public NullableType(Type type) : + base($"{type}?") => + Type = type; + + protected NullableType() + { + } + + public override void ResolveReference( + Type reference, + string refId, + ISet? visited = null) + { + if (Type == refId) + Type = reference; + else + Type.ResolveReference(reference, refId, visited); + } + + public override bool Equals(object? obj) + { + if (obj is NullableType that) + return Equals(Type, that.Type); + + return obj is NullType or Any; + } + + public override int GetHashCode() => + // ReSharper disable once NonReadonlyMemberInGetHashCode + Type.GetHashCode(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs b/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs new file mode 100644 index 00000000..0080c27f --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs @@ -0,0 +1,188 @@ +using System.Text; + +namespace HydraScript.Domain.IR.Types; + +public class ObjectType : NullableType +{ + private readonly Dictionary _properties; + private readonly HashSet _methods; + private readonly ObjectTypeHasher _hasher; + private readonly ObjectTypePrinter _serializer; + + public string LastAccessedMethod { get; private set; } = default!; + + public ObjectType(IEnumerable properties) + { + _properties = properties + .OrderBy(x => x.Id) + .ToDictionary( + x => x.Id, + x => x.Type); + _methods = new HashSet(); + + _hasher = new ObjectTypeHasher(this); + _serializer = new ObjectTypePrinter(this); + } + + public Type? this[string id] + { + get => _properties.GetValueOrDefault(id); + private set => _properties[id] = value!; + } + + public void AddMethod(string methodName) => + _methods.Add(methodName); + + public bool HasMethod(string methodName) + { + LastAccessedMethod = methodName; + return _methods.Contains(methodName); + } + + public override void ResolveReference( + Type reference, + string refId, + ISet? visited = null) + { + visited ??= new HashSet(); + if (!visited.Add(this)) + return; + + foreach (var key in _properties.Keys) + if (refId == this[key]!) + this[key] = reference; + else + this[key]!.ResolveReference(reference, refId, visited); + } + + public override bool Equals(object? obj) + { + if (obj is ObjectType that) + return ReferenceEquals(this, that) || + _properties.Count == that._properties.Count && + _properties.Zip(that._properties) + .All(pair => + pair.First.Key == pair.Second.Key && + pair.First.Value.Equals(pair.Second.Value)); + + return obj is NullType or Any; + } + + public override int GetHashCode() => + _hasher.HashObjectType(this); + + public override string ToString() + { + var result = _serializer.PrintObjectType(this); + _serializer.Clear(); + return result; + } + + private class ObjectTypeHasher + { + private readonly ObjectType _reference; + + public ObjectTypeHasher(ObjectType reference) => + _reference = reference; + + private int Hash(Type type) => type switch + { + ArrayType arrayType => HashArrayType(arrayType), + ObjectType objectType => HashObjectType(objectType), + NullableType nullableType => HashNullableType(nullableType), + _ => type.GetHashCode() + }; + + public int HashObjectType(ObjectType objectType) => + objectType._properties.Keys.Select( + key => HashCode.Combine( + key, + objectType[key]!.Equals(_reference) + ? "@this".GetHashCode() + : objectType[key]!.GetType().GetHashCode())) + .Aggregate(36, HashCode.Combine); + + private int HashArrayType(ArrayType arrayType) => + arrayType.Type.Equals(_reference) + ? "@this".GetHashCode() + : Hash(arrayType.Type); + + private int HashNullableType(NullableType nullableType) => + nullableType.Type.Equals(_reference) + ? "@this".GetHashCode() + : Hash(nullableType.Type); + } + + private class ObjectTypePrinter + { + private readonly ObjectType _reference; + private readonly ISet _visited; + + public ObjectTypePrinter(ObjectType reference) + { + _reference = reference; + _visited = new HashSet(); + } + + public void Clear() => _visited.Clear(); + + private string Print(Type type) => type switch + { + ArrayType arrayType => PrintArrayType(arrayType), + ObjectType objectType => PrintObjectType(objectType), + NullableType nullableType => PrintNullableType(nullableType), + _ => type.ToString() + }; + + public string PrintObjectType(ObjectType objectType) + { + if (_visited.Contains(objectType)) + return string.Empty; + if (!objectType.Equals(_reference)) + _visited.Add(objectType); + + var sb = new StringBuilder("{"); + foreach (var key in objectType._properties.Keys) + { + var type = objectType[key]; + var prop = $"{key}: "; + + if (type!.Equals(_reference)) + prop += "@this"; + else + { + var printedType = Print(type); + prop += string.IsNullOrEmpty(printedType) + ? key + : printedType; + } + + sb.Append(prop).Append(';'); + } + + return sb.Append('}').ToString(); + } + + private string PrintArrayType(ArrayType arrayType) + { + var sb = new StringBuilder(); + sb.Append(arrayType.Type.Equals(_reference) + ? "@this" + : Print(arrayType.Type)); + + return sb.Append("[]").ToString(); + } + + private string PrintNullableType(NullableType nullableType) + { + var sb = new StringBuilder(); + sb.Append(nullableType.Type.Equals(_reference) + ? "@this" + : Print(nullableType.Type)); + + return sb.Append('?').ToString(); + } + } +} + +public record PropertyType(string Id, Type Type); \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Type.cs b/src/Domain/HydraScript.Domain.IR/Types/Type.cs new file mode 100644 index 00000000..26b33b42 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Type.cs @@ -0,0 +1,42 @@ +namespace HydraScript.Domain.IR.Types; + +public class Type +{ + private readonly string _name = default!; + + protected Type() + { + } + + public Type(string name) => + _name = name; + + public virtual void ResolveReference( + Type reference, + string refId, + ISet? visited = null) + { + } + + public override bool Equals(object? obj) => + obj switch + { + Any => true, + Type that => _name == that._name, + _ => false + }; + + public override int GetHashCode() => + _name.GetHashCode(); + + public override string ToString() => _name; + + public static implicit operator Type(string alias) => + new(alias); + + public static bool operator ==(Type left, Type right) => + Equals(left, right); + + public static bool operator !=(Type left, Type right) => + !(left == right); +} \ No newline at end of file diff --git a/src/HydraScript/ExecuteCommand.cs b/src/HydraScript/ExecuteCommand.cs new file mode 100644 index 00000000..343a7355 --- /dev/null +++ b/src/HydraScript/ExecuteCommand.cs @@ -0,0 +1,23 @@ +using System.CommandLine; + +namespace HydraScript; + +public class ExecuteCommand : RootCommand +{ + public ExecuteCommand() : base("HydraScript interpreter") + { + PathArgument = new Argument( + name: "path", + description: "Path to input file"); + AddArgument(PathArgument); + + DumpOption = new Option( + ["-d", "--dump"], + getDefaultValue: () => false, + description: "Show dump data of interpreter"); + AddOption(DumpOption); + } + + public Argument PathArgument { get; } + public Option DumpOption { get; } +} \ No newline at end of file diff --git a/src/HydraScript/ExecuteCommandHandler.cs b/src/HydraScript/ExecuteCommandHandler.cs new file mode 100644 index 00000000..4e7b691f --- /dev/null +++ b/src/HydraScript/ExecuteCommandHandler.cs @@ -0,0 +1,45 @@ +using System.CommandLine.Invocation; +using HydraScript.Application.CodeGeneration; +using HydraScript.Application.StaticAnalysis.Exceptions; +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Infrastructure; + +namespace HydraScript; + +internal class ExecuteCommandHandler( + ISourceCodeProvider sourceCodeProvider, + IParser parser, + ICodeGenerator codeGenerator, + IVirtualMachine virtualMachine, + TextWriter writer) : ICommandHandler +{ + + public int Invoke(InvocationContext context) + { + try + { + var sourceCode = sourceCodeProvider.GetText(); + var ast = parser.Parse(sourceCode); + var instructions = codeGenerator.GetInstructions(ast); + virtualMachine.Run(instructions); + return 0; + } + catch (Exception ex) + when (ex is LexerException or ParserException or SemanticException) + { + writer.WriteLine(ex.Message); + return 1; + } + catch (Exception ex) + { + writer.WriteLine("Internal HydraScript Error"); + writer.WriteLine(ex); + return 2; + } + } + + public Task InvokeAsync(InvocationContext context) => + Task.FromResult(Invoke(context)); +} \ No newline at end of file diff --git a/src/HydraScript/HydraScript.csproj b/src/HydraScript/HydraScript.csproj new file mode 100644 index 00000000..1d44d2d3 --- /dev/null +++ b/src/HydraScript/HydraScript.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/src/HydraScript/Program.cs b/src/HydraScript/Program.cs new file mode 100644 index 00000000..23e01587 --- /dev/null +++ b/src/HydraScript/Program.cs @@ -0,0 +1,39 @@ +using System.CommandLine.Builder; +using System.CommandLine.Hosting; +using System.CommandLine.Parsing; +using HydraScript; +using HydraScript.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +return GetRunner(ConfigureHost).Invoke(args); + +public static partial class Program +{ + public static readonly ExecuteCommand Command = new(); + + public static Parser GetRunner(Action configureHost, bool useDefault = true) + { + var builder = new CommandLineBuilder(Command) + .UseHost(Host.CreateDefaultBuilder, configureHost); + if (useDefault) + builder = builder.UseDefaults(); + return builder.Build(); + } + + private static void ConfigureHost(IHostBuilder builder) => builder + .ConfigureServices((context, services) => + { + services.AddLogging(c => c.ClearProviders()); + var parseResult = context.GetInvocationContext().ParseResult; + var fileInfo = parseResult.GetValueForArgument(Command.PathArgument); + var dump = parseResult.GetValueForOption(Command.DumpOption); + services + .AddDomain() + .AddApplication() + .AddInfrastructure(dump, fileInfo); + }) + .UseDefaultServiceProvider((_, options) => options.ValidateScopes = true) + .UseCommandHandler(); +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj new file mode 100644 index 00000000..57b3ef29 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + true + + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + + + + + false + + + diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.TokenTypes.cs b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.TokenTypes.cs new file mode 100644 index 00000000..c9fbd3ce --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.TokenTypes.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace HydraScript.Infrastructure.LexerRegexGenerator; + +public partial class PatternGenerator +{ + private record TokenType( + string Tag, + string Pattern, + int Priority) + { + public string GetNamedRegex() => $"(?<{Tag}>{Pattern})"; + } + + [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + [JsonSerializable(typeof(IEnumerable))] + private partial class PatternGeneratorContext : JsonSerializerContext; +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs new file mode 100644 index 00000000..afab806c --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs @@ -0,0 +1,96 @@ +using System.Collections.Immutable; +using System.Text; +using System.Text.Json; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace HydraScript.Infrastructure.LexerRegexGenerator; + +[Generator] +public partial class PatternGenerator : IIncrementalGenerator +{ + private const string AttributeSourceCode = @"// + +namespace HydraScript.Infrastructure; + +[System.AttributeUsage(System.AttributeTargets.Class)] +public class PatternContainerAttribute(string json) : System.Attribute + where T : HydraScript.Domain.FrontEnd.Lexer.IGeneratedRegexContainer +{ + public string Json { get; } = json; +} +"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(ctx => ctx.AddSource( + "PatternContainerAttribute.g.cs", + SourceText.From(AttributeSourceCode, Encoding.UTF8))); + + var provider = context.SyntaxProvider + .ForAttributeWithMetadataName( + "HydraScript.Infrastructure.PatternContainerAttribute`1", + static (s, _) => IsSyntaxTargetForGeneration(s), + static (ctx, _) => GetTypeDeclarationForSourceGen(ctx)) + .Where(static x => x is not null) + .Select(static (x, _) => x!); + + context.RegisterImplementationSourceOutput(provider.Collect(), GenerateCode); + } + + private static bool IsSyntaxTargetForGeneration(SyntaxNode node) => + node is ClassDeclarationSyntax candidate && + candidate.Modifiers.Any(SyntaxKind.PartialKeyword) && + candidate.Modifiers.Any(SyntaxKind.InternalKeyword); + + private static RegexContainerInfo? GetTypeDeclarationForSourceGen( + GeneratorAttributeSyntaxContext context) + { + var attribute = context.Attributes.FirstOrDefault(); + if (attribute is null) + return null; + var visitable = (ClassDeclarationSyntax)context.TargetNode; + var json = attribute.ConstructorArguments.First().Value!.ToString()!; + return new RegexContainerInfo( + ClassName: visitable.Identifier.Text, + json); + } + + private static void GenerateCode( + SourceProductionContext context, + ImmutableArray containerInfos) + { + foreach (var info in containerInfos) + { + var tokenTypes = JsonSerializer.Deserialize( + info.Json, + PatternGeneratorContext.Default.IEnumerableTokenType)! + .OrderBy(x => x.Priority) + .Concat([new TokenType("ERROR", @"\S+", int.MaxValue)]); + var pattern = string.Join('|', tokenTypes.Select(t => t.GetNamedRegex())); + + var code = $@"// + +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Infrastructure; + +internal partial class {info.ClassName} +{{ + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string Pattern = + """""" + {pattern} + """"""; +}} +"; + context.AddSource($"{info.ClassName}.g.cs", SourceText.From(code, Encoding.UTF8)); + } + } + + private record RegexContainerInfo( + string ClassName, + string Json); +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/Properties/launchSettings.json b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/Properties/launchSettings.json new file mode 100644 index 00000000..4d7712ba --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../HydraScript.Infrastructure/HydraScript.Infrastructure.csproj" + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/CodeGenerator.cs b/src/Infrastructure/HydraScript.Infrastructure/CodeGenerator.cs new file mode 100644 index 00000000..a02152ab --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/CodeGenerator.cs @@ -0,0 +1,30 @@ +using HydraScript.Application.CodeGeneration; +using HydraScript.Application.StaticAnalysis; +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.FrontEnd.Parser; +using Microsoft.Extensions.DependencyInjection; +using Visitor.NET; + +namespace HydraScript.Infrastructure; + +internal class CodeGenerator : ICodeGenerator +{ + private readonly IStaticAnalyzer _staticAnalyzer; + private readonly IVisitor _visitor; + + public CodeGenerator( + IStaticAnalyzer staticAnalyzer, + [FromKeyedServices("instructions")] + IVisitor visitor) + { + _staticAnalyzer = staticAnalyzer; + _visitor = visitor; + } + + public AddressedInstructions GetInstructions(IAbstractSyntaxTree ast) + { + _staticAnalyzer.Analyze(ast); + var root = ast.Root; + return root.Accept(_visitor); + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs new file mode 100644 index 00000000..67df795e --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Lexer; + +namespace HydraScript.Infrastructure; + +[PatternContainer(TokenTypesJson.String)] +internal partial class GeneratedRegexContainer : IGeneratedRegexContainer +{ + [GeneratedRegex("""(?[/]{2}.*)|(?[0-9]+[.][0-9]+)|(?[0-9]+)|(?null)|(?true|false)|(?\"(\\.|[^"\\])*\")|(?let|const|function|if|else|while|break|continue|return|as|type)|(?[+]{1,2}|[-]|[*]|[/]|[%]|([!]|[=])[=]|([<]|[>])[=]?|[!]|[|]{2}|[&]{2}|[~]|[:]{2})|(?[a-zA-Z][a-zA-Z0-9]*)|(?[?])|(?[:])|(?[;])|(?[=])|(?[,])|(?[{])|(?[}])|(?[(])|(?[)])|(?[.])|(?[[])|(?[]])|(?\S+)""")] + public static partial Regex GetRegex(); +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/HydraScript.Infrastructure.csproj b/src/Infrastructure/HydraScript.Infrastructure/HydraScript.Infrastructure.csproj new file mode 100644 index 00000000..7056f88c --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/HydraScript.Infrastructure.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + + + + + false + + + diff --git a/src/Infrastructure/HydraScript.Infrastructure/ISourceCodeProvider.cs b/src/Infrastructure/HydraScript.Infrastructure/ISourceCodeProvider.cs new file mode 100644 index 00000000..a3c77813 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/ISourceCodeProvider.cs @@ -0,0 +1,22 @@ +using System.IO.Abstractions; +using Microsoft.Extensions.Options; + +namespace HydraScript.Infrastructure; + +public interface ISourceCodeProvider +{ + string GetText(); +} + +internal class SourceCodeProvider( + IFileSystem fileSystem, + IOptions inputFile) : ISourceCodeProvider +{ + private readonly InputFile _inputFile = inputFile.Value; + + public string GetText() + { + var inputFilePath = _inputFile.Info.FullName; + return fileSystem.File.ReadAllText(inputFilePath); + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/InputFile.cs b/src/Infrastructure/HydraScript.Infrastructure/InputFile.cs new file mode 100644 index 00000000..f6de800a --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/InputFile.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Infrastructure; + +public class InputFile +{ + public required FileInfo Info { get; init; } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingLexer.cs b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingLexer.cs new file mode 100644 index 00000000..ccf98f5c --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingLexer.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; +using HydraScript.Domain.FrontEnd.Lexer; +using Microsoft.Extensions.Options; + +namespace HydraScript.Infrastructure.Logging; + +internal class LoggingLexer( + ILexer lexer, + IFileSystem fileSystem, + IOptions inputFile) : ILexer +{ + private readonly InputFile _inputFile = inputFile.Value; + + [ExcludeFromCodeCoverage] + public IStructure Structure => lexer.Structure; + + public List GetTokens(string text) + { + var tokens = lexer.GetTokens(text); + var fileName = _inputFile.Info.Name.Split(".js")[0]; + fileSystem.File.WriteAllText( + $"{fileName}.tokens", + lexer.ToString()); + return tokens; + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingParser.cs b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingParser.cs new file mode 100644 index 00000000..d6f3c36d --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingParser.cs @@ -0,0 +1,15 @@ +using System.IO.Abstractions; +using HydraScript.Domain.FrontEnd.Parser; + +namespace HydraScript.Infrastructure.Logging; + +internal class LoggingParser(IParser parser, IFileSystem fileSystem) : IParser +{ + public IAbstractSyntaxTree Parse(string text) + { + var ast = parser.Parse(text); + var astDot = ast.ToString(); + fileSystem.File.WriteAllText("ast.dot", astDot); + return ast; + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingVirtualMachine.cs b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingVirtualMachine.cs new file mode 100644 index 00000000..321ea676 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/Logging/LoggingVirtualMachine.cs @@ -0,0 +1,25 @@ +using System.IO.Abstractions; +using HydraScript.Domain.BackEnd; +using Microsoft.Extensions.Options; + +namespace HydraScript.Infrastructure.Logging; + +internal class LoggingVirtualMachine( + IVirtualMachine virtualMachine, + IFileSystem fileSystem, + IOptions inputFile) : IVirtualMachine +{ + private readonly InputFile _inputFile = inputFile.Value; + + public IExecuteParams ExecuteParams => virtualMachine.ExecuteParams; + + public void Run(AddressedInstructions instructions) + { + var fileName = _inputFile.Info.Name.Split(".js")[0]; + fileSystem.File.WriteAllLines( + $"{fileName}.tac", + instructions.Select(i => i.ToString()!)); + + virtualMachine.Run(instructions); + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/ServiceCollectionExtensions.cs b/src/Infrastructure/HydraScript.Infrastructure/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..ca07aca8 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/ServiceCollectionExtensions.cs @@ -0,0 +1,56 @@ +using System.IO.Abstractions; +using HydraScript.Application.CodeGeneration; +using HydraScript.Application.StaticAnalysis; +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl; +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.Impl; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl; +using HydraScript.Infrastructure.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace HydraScript.Infrastructure; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddDomain(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(Console.Out); + services.AddSingleton(); + + return services; + } + + public static IServiceCollection AddApplication(this IServiceCollection services) => services + .AddStaticAnalysis() + .AddCodeGeneration(); + + public static void AddInfrastructure( + this IServiceCollection services, + bool dump, + FileInfo inputFileInfo) + { + services.AddSingleton(); + services.AddSingleton(Options.Create(new InputFile { Info = inputFileInfo })); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + + if (dump) + { + services.Decorate(); + services.Decorate(); + services.Decorate(); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/StaticAnalyzer.cs b/src/Infrastructure/HydraScript.Infrastructure/StaticAnalyzer.cs new file mode 100644 index 00000000..62942cea --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/StaticAnalyzer.cs @@ -0,0 +1,22 @@ +using HydraScript.Application.StaticAnalysis; +using HydraScript.Domain.FrontEnd.Parser; +using Visitor.NET; +using Type = HydraScript.Domain.IR.Types.Type; + +namespace HydraScript.Infrastructure; + +internal class StaticAnalyzer( + IEnumerable> preAnalyzers, + IVisitor analyzer) : IStaticAnalyzer +{ + public void Analyze(IAbstractSyntaxTree ast) + { + var root = ast.Root; + foreach (var preAnalyzer in preAnalyzers) + { + root.Accept(preAnalyzer); + } + + root.Accept(analyzer); + } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/TokenTypesProvider.cs b/src/Infrastructure/HydraScript.Infrastructure/TokenTypesProvider.cs new file mode 100644 index 00000000..44bd978b --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure/TokenTypesProvider.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; + +namespace HydraScript.Infrastructure; + +internal partial class TokenTypesProvider : ITokenTypesProvider +{ + public IEnumerable GetTokenTypes() => + JsonSerializer.Deserialize( + TokenTypesJson.String, + TokenTypesProviderContext.Default.IEnumerableTokenType)!; + + [ExcludeFromCodeCoverage] + private class TokenTypesReadConverter : JsonConverter> + { + public override IEnumerable Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var root = JsonElement.ParseValue(ref reader); + var tokenTypes = root.EnumerateArray() + .Select(element => + { + var tag = element.GetProperty("tag").GetString()!; + var priority = element.GetProperty("priority").GetInt32(); + + var ignorable = element.TryGetProperty("canIgnore", out var canIgnore); + + var tokenType = ignorable && canIgnore.GetBoolean() + ? new IgnorableType(tag) + : new TokenType(tag); + return new PrioritizedTokenType(tokenType, priority); + }) + .OrderBy(x => x.Priority) + .Select(x => x.TokenType); + return tokenTypes; + } + + public override void Write( + Utf8JsonWriter writer, + IEnumerable value, + JsonSerializerOptions options) => throw new NotSupportedException(); + + private record PrioritizedTokenType(TokenType TokenType, int Priority); + } + + [JsonSourceGenerationOptions(Converters = [typeof(TokenTypesReadConverter)])] + [JsonSerializable(typeof(IEnumerable))] + private partial class TokenTypesProviderContext : JsonSerializerContext; +} \ No newline at end of file diff --git a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj new file mode 100644 index 00000000..682db004 --- /dev/null +++ b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + true + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs new file mode 100644 index 00000000..10840d0b --- /dev/null +++ b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs @@ -0,0 +1,71 @@ +using System.Diagnostics; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace HydraScript.Infrastructure.LexerRegexGenerator.Tests; + +public class PatternGeneratorTests +{ + [Fact] + public void Initialize_PatternContainerMarked_CorrectlyGenerated() + { + var inputCompilation = CreateCompilation( + """ + using System.Text.RegularExpressions; + using HydraScript.Domain.FrontEnd.Lexer; + + namespace HydraScript.Infrastructure; + + [PatternContainer("[{ \"tag\": \"Number\", \"pattern\": \"[0-9]+\", \"priority\": 2 }, { \"tag\": \"Word\", \"pattern\": \"[a-zA-Z]+\", \"priority\": 1 }]")] + internal partial class TestPatternContainer : IGeneratedRegexContainer + { + public static Regex GetRegex() => throw new NotImplementedException(); + } + """); + + const string expectedSource = +"""" +// + +using System.Diagnostics.CodeAnalysis; + +namespace HydraScript.Infrastructure; + +internal partial class TestPatternContainer +{ + [StringSyntax(StringSyntaxAttribute.Regex)] + public const string Pattern = + """ + (?[a-zA-Z]+)|(?[0-9]+)|(?\S+) + """; +} + +""""; + + var generator = new PatternGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, + out var diagnostics); + Debug.Assert(diagnostics.IsEmpty); + Debug.Assert(outputCompilation.SyntaxTrees.Count() == 3); + + var runResult = driver.GetRunResult(); + + var generatedFileSyntax = runResult.GeneratedTrees + .Single(t => t.FilePath.EndsWith("TestPatternContainer.g.cs")); + + Assert.Equal( + expectedSource, + generatedFileSyntax.GetText().ToString(), + ignoreLineEndingDifferences: true); + } + + private static CSharpCompilation CreateCompilation(string source) => + CSharpCompilation.Create("compilation", + new[] { CSharpSyntaxTree.ParseText(source) }, + new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) }, + new CSharpCompilationOptions(OutputKind.ConsoleApplication)); +} \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/GlobalUsings.cs b/tests/HydraScript.IntegrationTests/GlobalUsings.cs new file mode 100644 index 00000000..75a33966 --- /dev/null +++ b/tests/HydraScript.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,3 @@ +// Global using directives + +global using Xunit; \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/HydraScript.IntegrationTests.csproj b/tests/HydraScript.IntegrationTests/HydraScript.IntegrationTests.csproj new file mode 100644 index 00000000..4136bb7e --- /dev/null +++ b/tests/HydraScript.IntegrationTests/HydraScript.IntegrationTests.csproj @@ -0,0 +1,141 @@ + + + + net8.0 + enable + enable + true + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + Samples\abs.js + Always + + + Samples\arraddremove.js + Always + + + Samples\arreditread.js + Always + + + Samples\ceil.js + Always + + + Samples\cycled.js + Always + + + Samples\defaultarray.js + Always + + + Samples\equals.js + Always + + + Samples\exprtest.js + Always + + + Samples\fastpow.js + Always + + + Samples\forwardref.js + Always + + + Samples\gcd.js + Always + + + Samples\lcm.js + Always + + + Samples\linkedlist.js + Always + + + Samples\objeditread.js + Always + + + Samples\posneg.js + Always + + + Samples\prime.js + Always + + + Samples\primefactor.js + Always + + + Samples\quicksort.js + Always + + + Samples\range.js + Always + + + Samples\recur.js + Always + + + Samples\searchinll.js + Always + + + Samples\settable.js + Always + + + Samples\squareroot.js + Always + + + Samples\summator.js + Always + + + Samples\tern.js + Always + + + Samples\this.js + Always + + + Samples\typeresolving.js + Always + + + Samples\vec2d.js + Always + + + + diff --git a/tests/HydraScript.IntegrationTests/Properties/AssemblyInfo.cs b/tests/HydraScript.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4c5fd931 --- /dev/null +++ b/tests/HydraScript.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: AssemblyTrait("Category", "Integration")] \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/SuccessfulProgramsTests.cs b/tests/HydraScript.IntegrationTests/SuccessfulProgramsTests.cs new file mode 100644 index 00000000..52f2f27a --- /dev/null +++ b/tests/HydraScript.IntegrationTests/SuccessfulProgramsTests.cs @@ -0,0 +1,52 @@ +using System.CommandLine.Parsing; +using FluentAssertions; +using Xunit.Abstractions; + +namespace HydraScript.IntegrationTests; + +public class SuccessfulProgramsTests( + TestHostFixture fixture, + ITestOutputHelper testOutputHelper) : IClassFixture +{ + [Theory, MemberData(nameof(SuccessfulProgramsNames))] + public void Invoke_NoError_ReturnCodeIsZero(string fileName) + { + var runner = fixture.GetRunner(testOutputHelper); + var code = runner.Invoke([$"Samples/{fileName}"]); + testOutputHelper.WriteLine(fixture.Writer.ToString()); + code.Should().Be(0); + } + + public static TheoryData SuccessfulProgramsNames => + new( + [ + "abs.js", + "arraddremove.js", + "arreditread.js", + "ceil.js", + "cycled.js", + "defaultarray.js", + "equals.js", + "exprtest.js", + "fastpow.js", + "forwardref.js", + "gcd.js", + "lcm.js", + "linkedlist.js", + "objeditread.js", + "posneg.js", + "prime.js", + "primefactor.js", + "quicksort.js", + "range.js", + "recur.js", + "searchinll.js", + "settable.js", + "squareroot.js", + "summator.js", + "tern.js", + "this.js", + "typeresolving.js", + "vec2d.js", + ]); +} \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/TestHostFixture.cs b/tests/HydraScript.IntegrationTests/TestHostFixture.cs new file mode 100644 index 00000000..28dec4ae --- /dev/null +++ b/tests/HydraScript.IntegrationTests/TestHostFixture.cs @@ -0,0 +1,37 @@ +using System.CommandLine.Hosting; +using System.CommandLine.Parsing; +using HydraScript.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace HydraScript.IntegrationTests; + +public class TestHostFixture : IDisposable +{ + public readonly TextWriter Writer = new StringWriter(); + + public Parser GetRunner(ITestOutputHelper testOutputHelper) => + Program.GetRunner(configureHost: builder => builder + .ConfigureLogging(x => + { + x.ClearProviders(); + x.AddXUnit(testOutputHelper); + }) + .ConfigureServices((context, services) => + { + var parseResult = context.GetInvocationContext().ParseResult; + var fileInfo = parseResult.GetValueForArgument(Program.Command.PathArgument); + var dump = parseResult.GetValueForOption(Program.Command.DumpOption); + services + .AddDomain() + .AddApplication() + .AddInfrastructure(dump, fileInfo); + services.AddSingleton(Writer); + }) + .UseCommandHandler(), + useDefault: false); + + public void Dispose() => Writer.Dispose(); +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/GlobalUsings.cs b/tests/HydraScript.Tests/GlobalUsings.cs new file mode 100644 index 00000000..c59d4898 --- /dev/null +++ b/tests/HydraScript.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +// Global using directives + +global using FluentAssertions; +global using Type = HydraScript.Domain.IR.Types.Type; \ No newline at end of file diff --git a/tests/HydraScript.Tests/Helpers/MockExtensions.cs b/tests/HydraScript.Tests/Helpers/MockExtensions.cs new file mode 100644 index 00000000..a64adcf0 --- /dev/null +++ b/tests/HydraScript.Tests/Helpers/MockExtensions.cs @@ -0,0 +1,31 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using Moq; + +namespace HydraScript.Tests.Helpers; + +public static class MockExtensions +{ + public static Mock Trackable(this Mock halt) + { + halt.Setup(x => x.Execute(It.IsAny())) + .Returns(new HashAddress(seed: 0)).Verifiable(); + halt.Setup(x => x.End).Returns(true); + return halt; + } + + public static Mock ToInstructionMock(this int number) + { + var result = new Mock(); + result.SetupAllProperties(); + + result.Setup(x => x.GetHashCode()) + .Returns(number); + + result.Setup(x => x.ToString()) + .Returns(number.ToString()); + + return result; + } +} \ No newline at end of file diff --git a/Interpreter.Tests/Interpreter.Tests.csproj b/tests/HydraScript.Tests/HydraScript.Tests.csproj similarity index 58% rename from Interpreter.Tests/Interpreter.Tests.csproj rename to tests/HydraScript.Tests/HydraScript.Tests.csproj index 4a4d264c..039cf30e 100644 --- a/Interpreter.Tests/Interpreter.Tests.csproj +++ b/tests/HydraScript.Tests/HydraScript.Tests.csproj @@ -1,37 +1,34 @@ - net7.0 - false + net8.0 enable - - - - true - cobertura + enable + true + true - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + diff --git a/Interpreter.Tests/Properties/AssemblyInfo.cs b/tests/HydraScript.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from Interpreter.Tests/Properties/AssemblyInfo.cs rename to tests/HydraScript.Tests/Properties/AssemblyInfo.cs diff --git a/tests/HydraScript.Tests/Stubs/SemanticExceptionStub.cs b/tests/HydraScript.Tests/Stubs/SemanticExceptionStub.cs new file mode 100644 index 00000000..527819c7 --- /dev/null +++ b/tests/HydraScript.Tests/Stubs/SemanticExceptionStub.cs @@ -0,0 +1,5 @@ +using HydraScript.Application.StaticAnalysis.Exceptions; + +namespace HydraScript.Tests.Stubs; + +public class SemanticExceptionStub : SemanticException { } \ No newline at end of file diff --git a/tests/HydraScript.Tests/TestData/InstructionsData.cs b/tests/HydraScript.Tests/TestData/InstructionsData.cs new file mode 100644 index 00000000..7c07faee --- /dev/null +++ b/tests/HydraScript.Tests/TestData/InstructionsData.cs @@ -0,0 +1,132 @@ +using System.Collections; +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; +using HydraScript.Domain.BackEnd.Impl.Values; + +namespace HydraScript.Tests.TestData; + +public class InstructionsData : IEnumerable +{ + public IEnumerator GetEnumerator() + { + yield return new object[] + { + new AsString(new Name("num")) + { + Left = "str" + }, + "str = num as string" + }; + yield return new object[] + { + new BeginBlock(BlockType.Function, blockId: "func") + { + Address = new Label("Start_func") + }, + "Start_func:\n\tBeginFunction func" + }; + yield return new object[] + { + new CallFunction(new FunctionInfo("func"), false), + "Call func" + }; + yield return new object[] + { + new CallFunction(new FunctionInfo("func"), true) + { + Left = "ret" + }, + "ret = Call func" + }; + yield return new object[] + { + new CreateArray("arr", 5), + "array arr = [5]" + }; + yield return new object[] + { + new CreateObject("obj"), + "object obj = {}" + }; + yield return new object[] + { + new DotAssignment("obj", new Constant("prop"), new Constant(3)), + "obj.prop = 3" + }; + yield return new object[] + { + new EndBlock(BlockType.Function, blockId: "func") + { + Address = new Label("End_func") + }, + "End_func:\n\tEndFunction func" + }; + yield return new object[] + { + new Goto(new Label("10")), + "Goto 10" + }; + yield return new object[] + { + new Halt(), + "End" + }; + yield return new object[] + { + new IfNotGoto(new Name("test"), new Label("17")), + "IfNot test Goto 17" + }; + yield return new object[] + { + new IndexAssignment("arr", new Constant(1), new Constant(1)), + "arr[1] = 1" + }; + yield return new object[] + { + new Print(new Name("str")), + "Print str" + }; + yield return new object[] + { + new PushParameter(new Name("value")), + "PushParameter value" + }; + yield return new object[] + { + new PopParameter("param"), + "PopParameter param" + }; + yield return new object[] + { + new RemoveFromArray("arr", new Constant(0)), + "RemoveFrom arr at 0" + }; + yield return new object[] + { + new Return(), + "Return" + }; + yield return new object[] + { + new Return(new Name("result")), + "Return result" + }; + yield return new object[] + { + new Simple("a", (new Name("b"), new Name("c")), "+"), + "a = b + c" + }; + yield return new object[] + { + new Simple("b", (null, new Name("c")), "-"), + "b = -c" + }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/Interpreter.Tests/TestData/LexerData.cs b/tests/HydraScript.Tests/TestData/LexerData.cs similarity index 95% rename from Interpreter.Tests/TestData/LexerData.cs rename to tests/HydraScript.Tests/TestData/LexerData.cs index a6addcfb..7d60d269 100644 --- a/Interpreter.Tests/TestData/LexerData.cs +++ b/tests/HydraScript.Tests/TestData/LexerData.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace Interpreter.Tests.TestData; +namespace HydraScript.Tests.TestData; public class LexerSuccessData : IEnumerable { diff --git a/Interpreter.Tests/TestData/ParserData.cs b/tests/HydraScript.Tests/TestData/ParserData.cs similarity index 52% rename from Interpreter.Tests/TestData/ParserData.cs rename to tests/HydraScript.Tests/TestData/ParserData.cs index 8a9cda65..317c7e12 100644 --- a/Interpreter.Tests/TestData/ParserData.cs +++ b/tests/HydraScript.Tests/TestData/ParserData.cs @@ -1,17 +1,24 @@ using System.Collections; -namespace Interpreter.Tests.TestData; +namespace HydraScript.Tests.TestData; public class ParserSuccessTestData : IEnumerable { public IEnumerator GetEnumerator() { + yield return new object[] { "-21" }; + yield return new object[] { "!false" }; + yield return new object[] { "~[]" }; + yield return new object[] { "x = ([1,2] ++ [3,4])::0" }; yield return new object[] {"i[0].j"}; yield return new object[] {"i[0].j()"}; yield return new object[] {"i = 1"}; yield return new object[] {"i[0] = 1"}; yield return new object[] {"i[a.b][1].x(1)"}; yield return new object[] {"(1 + 2) * (3 - (2 / 2)) as string"}; + yield return new object[] { "return {x:1;y:2;}" }; + yield return new object[] { "while (~arr != 0) { arr::0 continue }" }; + yield return new object[] { "if (!(true || (false && false))) { break } else { break }" }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/tests/HydraScript.Tests/Unit/BackEnd/AddressedInstructionsTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/AddressedInstructionsTests.cs new file mode 100644 index 00000000..821f1d73 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/BackEnd/AddressedInstructionsTests.cs @@ -0,0 +1,95 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Tests.Helpers; +using Xunit; + +namespace HydraScript.Tests.Unit.BackEnd; + +public class AddressedInstructionsTests +{ + [Fact] + public void EnumerationPreservedAfterRemovalTest() + { + AddressedInstructions instructions = + [ + new AsString(new Constant(2)) + { + Left = "s" + }, + new Print(new Name("s")), + new Halt() + ]; + + instructions.Remove(instructions[instructions.Start.Next]); + + Assert.Same(instructions.Last(), instructions[instructions.Start.Next]); + } + + [Fact] + public void RemovalOfLastDoesNotThrowTest() + { + AddressedInstructions instructions = + [ + new AsString(new Constant(2)), + new Halt() + ]; + + Assert.Null(Record.Exception(() => instructions.Remove(instructions.Last()))); + Assert.Null(instructions.Start.Next); + } + + [Fact] + public void ReplacementPreservesOrderTest() + { + var instructions = new AddressedInstructions + { + new Simple("a", (new Constant(1), new Constant(2)), "-"), + { + new AsString(new Constant(true)) + { Left = "s" }, + "as_str" + }, + new Print(new Name("s")) + }; + + var old = instructions[new Label("as_str")]; + var @new = new AsString(new Name("a")) { Left = "s" }; + instructions.Replace(old, @new); + + var prev = instructions.First(); + var next = instructions.Last(); + + Assert.Same(@new, instructions[prev.Address.Next]); + Assert.Same(next, instructions[@new.Address.Next]); + } + + [Fact] + public void GetEnumeratorTests() + { + AddressedInstructions collection = []; + collection.Add(1.ToInstructionMock().Object); + + AddressedInstructions collectionToAdd = + [ + 2.ToInstructionMock().Object, + 3.ToInstructionMock().Object, + 4.ToInstructionMock().Object + ]; + + collection.AddRange(collectionToAdd); + + collection.Add(5.ToInstructionMock().Object); + + Assert.Collection( + collection.Select(x => x.ToString()), + x => Assert.Equal("1", x), + x => Assert.Equal("2", x), + x => Assert.Equal("3", x), + x => Assert.Equal("4", x), + x => Assert.Equal("5", x) + ); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/BackEnd/CallTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/CallTests.cs new file mode 100644 index 00000000..e37a1948 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/BackEnd/CallTests.cs @@ -0,0 +1,18 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using Xunit; + +namespace HydraScript.Tests.Unit.BackEnd; + +public class CallTests +{ + [Fact] + public void ToStringCorrect() + { + var call = new Call( + new Label("9"), + new FunctionInfo("func")); + const string expected = "9:\n\t: => Start_func:\n\t: func"; + Assert.Equal(expected, call.ToString()); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/BackEnd/HashAddressTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/HashAddressTests.cs new file mode 100644 index 00000000..63bb9755 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/BackEnd/HashAddressTests.cs @@ -0,0 +1,35 @@ +using HydraScript.Domain.BackEnd.Impl.Addresses; +using Xunit; + +namespace HydraScript.Tests.Unit.BackEnd; + +public class HashAddressTests +{ + [Fact] + public void EqualsReturnsFalseForTwoDifferentObjectsWithSameSeed() + { + const int seed = 1; + + var addressOne = new HashAddress(seed); + var addressTwo = new HashAddress(seed); + + Assert.NotEqual(addressOne, addressTwo); + } + + [Fact] + public void EqualsReturnsTrueForTwoSameOjectsWithSameSeed() + { + var address = new HashAddress(1); + + Assert.Equal(address, address); + } + + [Fact] + public void EqualsReturnsFalseForTwoObjectsWithDifferentSeed() + { + var addressOne = new HashAddress(0); + var addressTwo = new HashAddress(1); + + Assert.NotEqual(addressOne, addressTwo); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/BackEnd/InstructionsTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/InstructionsTests.cs new file mode 100644 index 00000000..3f48ec2b --- /dev/null +++ b/tests/HydraScript.Tests/Unit/BackEnd/InstructionsTests.cs @@ -0,0 +1,24 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; +using HydraScript.Tests.TestData; +using Moq; +using Xunit; + +namespace HydraScript.Tests.Unit.BackEnd; + +public class InstructionsTests +{ + [Theory] + [ClassData(typeof(InstructionsData))] + public void ToStringCorrectTest(IExecutableInstruction instruction, string expected) => + Assert.Equal(expected, instruction.ToString()); + + [Fact] + public void GotoJumpChangedTest() + { + var @goto = new Goto(new Label("1")); + @goto.SetJump(new Label("5")); + Assert.Equal(new Label("5"), @goto.Execute(Mock.Of())); + } +} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/BackEnd/ValuesTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/ValuesTests.cs similarity index 83% rename from Interpreter.Tests/Unit/BackEnd/ValuesTests.cs rename to tests/HydraScript.Tests/Unit/BackEnd/ValuesTests.cs index 20dc7dc6..7b9ffa62 100644 --- a/Interpreter.Tests/Unit/BackEnd/ValuesTests.cs +++ b/tests/HydraScript.Tests/Unit/BackEnd/ValuesTests.cs @@ -1,7 +1,7 @@ -using Interpreter.Lib.BackEnd.Values; +using HydraScript.Domain.BackEnd.Impl.Values; using Xunit; -namespace Interpreter.Tests.Unit.BackEnd; +namespace HydraScript.Tests.Unit.BackEnd; public class ValuesTests { @@ -9,7 +9,7 @@ public class ValuesTests public void ConstantNotEqualToNameTest() { var name = new Name("a"); - var constant = new Constant("a", "a"); + var constant = new Constant("a"); Assert.False(name.Equals(constant)); Assert.False(constant.Equals(name)); @@ -37,7 +37,7 @@ public void NameEqualsCorrectTest() [Fact] public void ConstantEqualsCorrectTest() { - var constant1 = new Constant(1, "1"); + var constant1 = new Constant(1); var constant2 = new Constant(1, "1.0"); Assert.True(constant1.Equals(constant2)); diff --git a/tests/HydraScript.Tests/Unit/BackEnd/VirtualMachineTests.cs b/tests/HydraScript.Tests/Unit/BackEnd/VirtualMachineTests.cs new file mode 100644 index 00000000..5c62b90f --- /dev/null +++ b/tests/HydraScript.Tests/Unit/BackEnd/VirtualMachineTests.cs @@ -0,0 +1,164 @@ +using HydraScript.Domain.BackEnd; +using HydraScript.Domain.BackEnd.Impl; +using HydraScript.Domain.BackEnd.Impl.Addresses; +using HydraScript.Domain.BackEnd.Impl.Instructions; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Create; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithAssignment.ComplexData.Write; +using HydraScript.Domain.BackEnd.Impl.Instructions.WithJump; +using HydraScript.Domain.BackEnd.Impl.Values; +using HydraScript.Tests.Helpers; +using Moq; +using Xunit; + +namespace HydraScript.Tests.Unit.BackEnd; + +public class VirtualMachineTests +{ + private readonly IVirtualMachine _vm; + + public VirtualMachineTests() + { + _vm = new VirtualMachine(TextWriter.Null); + } + + [Fact] + public void CorrectPrintToOutTest() + { + var writer = new Mock(); + writer.Setup(x => x.WriteLine(It.IsAny())) + .Verifiable(); + + var exParams = new Mock(); + exParams.Setup(x => x.CallStack).Returns(new Stack()); + exParams.Setup(x => x.Frames).Returns(new Stack(new[] { new Frame(new HashAddress(0)) })); + exParams.Setup(x => x.Arguments).Returns(new Queue()); + exParams.Setup(x => x.Writer).Returns(writer.Object); + + var print = new Print(new Constant(223)) + { + Address = new HashAddress(1) + }; + + print.Execute(exParams.Object); + writer.Verify(x => x.WriteLine( + It.Is(v => v!.Equals(223)) + ), Times.Once()); + } + + [Fact] + public void ProgramWithoutHaltWillNotRunTest() + { + var program = new AddressedInstructions(); + Assert.Throws(() => _vm.Run(program)); + + program.Add(new Halt()); + Assert.Null(Record.Exception(() => _vm.Run(program))); + } + + [Fact] + public void VirtualMachineFramesClearedAfterExecutionTest() + { + AddressedInstructions program = + [ + new Simple("a", (new Constant(1), new Constant(2)), "+"), + new AsString(new Name("a")) + { + Left = "s" + }, + new Halt() + ]; + + _vm.Run(program); + Assert.Empty(_vm.ExecuteParams.Frames); + } + + [Fact] + public void VirtualMachineHandlesRecursionTest() + { + var halt = new Mock().Trackable(); + var factorial = new FunctionInfo("fact"); + var program = new AddressedInstructions + { + new Goto(factorial.End), + { new BeginBlock(BlockType.Function, blockId: factorial.ToString()), factorial.Start.Name }, + new PopParameter("n"), + new Simple("_t2", (new Name("n"), new Constant(2)), "<"), + new IfNotGoto(new Name("_t2"), new Label("5")), + new Return(new Name("n")), + { new Simple("_t5", (new Name("n"), new Constant(1)), "-"), "5" }, + new PushParameter(new Name("_t5")), + new CallFunction(factorial, true) + { + Left = "f" + }, + new Simple("_t8", (new Name("n"), new Name("f")), "*"), + new Return(new Name("_t8")), + { new EndBlock(BlockType.Function, blockId: factorial.ToString()), factorial.End.Name }, + new PushParameter(new Constant(6)), + new CallFunction(factorial, true) + { + Left = "fa6" + }, + halt.Object + }; + + _vm.Run(program); + Assert.Empty(_vm.ExecuteParams.CallStack); + Assert.Empty(_vm.ExecuteParams.Arguments); + halt.Verify(x => x.Execute( + It.Is( + vm => + Convert.ToInt32(vm.Frames.Peek()["fa6"]) == 720)), + Times.Once()); + _vm.ExecuteParams.Frames.Pop(); + } + + [Fact] + public void CreateArrayReservesCertainSpaceTest() + { + var vm = new ExecuteParams(Console.Out); + vm.Frames.Push(new Frame(new HashAddress(0))); + + var createArray = new CreateArray("arr", 6) + { + Address = new HashAddress(1) + }; + createArray.Execute(vm); + Assert.Equal(6, ((List) vm.Frames.Peek()["arr"]!).Count); + + var indexAssignment = new IndexAssignment("arr", new Constant(0), new Constant(0)) + { + Address = new HashAddress(2) + }; + indexAssignment.Execute(vm); + Assert.Equal(0, ((List) vm.Frames.Peek()["arr"]!)[0]); + + var removeFromArray = new RemoveFromArray("arr", new Constant(5)) + { + Address = new HashAddress(3) + }; + removeFromArray.Execute(vm); + Assert.Equal(5, ((List) vm.Frames.Peek()["arr"]!).Count); + } + + [Fact] + public void ObjectCreationTest() + { + var halt = new Mock().Trackable(); + AddressedInstructions program = + [ + new CreateObject("obj"), + new DotAssignment("obj", new Constant("prop"), new Constant(null, "null")), + halt.Object + ]; + + _vm.Run(program); + halt.Verify(x => x.Execute( + It.Is( + vm => + ((Dictionary)vm.Frames.Peek()["obj"]!)["prop"] == null)), + Times.Once()); + _vm.ExecuteParams.Frames.Pop(); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/FrontEnd/RegexLexerTests.cs b/tests/HydraScript.Tests/Unit/FrontEnd/RegexLexerTests.cs new file mode 100644 index 00000000..454921e2 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/FrontEnd/RegexLexerTests.cs @@ -0,0 +1,47 @@ +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.Impl; +using HydraScript.Infrastructure; +using HydraScript.Tests.TestData; +using Xunit; + +namespace HydraScript.Tests.Unit.FrontEnd; + +public class RegexLexerTests +{ + private readonly RegexLexer _regexLexer = new( + new Structure(new TokenTypesProvider()), + new TextCoordinateSystemComputer()); + + [Theory] + [ClassData(typeof(LexerSuccessData))] + public void LexerDoesNotThrowTest(string text) => + Assert.Null(Record.Exception(() => _regexLexer.GetTokens(text))); + + [Theory] + [ClassData(typeof(LexerFailData))] + public void LexerThrowsErrorTest(string text) => + Assert.Throws(() => _regexLexer.GetTokens(text)); + + [Fact] + public void LexerToStringCorrectTest() + { + const string text = "8"; + var tokens = _regexLexer.GetTokens(text); + Assert.Contains("EOP", _regexLexer.ToString()); + Assert.Equal("IntegerLiteral (1, 1)-(1, 2): 8", tokens.First().ToString()); + } + + [Fact] + public void EmptyTextTest() => + Assert.NotEmpty(_regexLexer.GetTokens("")); + + [Fact] + public void GetTokensSkipIgnorableTypesTest() + { + const string text = @" + let x = 1 // int + "; + var tokens = _regexLexer.GetTokens(text); + Assert.DoesNotContain(_regexLexer.Structure.FindByTag("Comment"), tokens.Select(x => x.Type)); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/FrontEnd/StructureTests.cs b/tests/HydraScript.Tests/Unit/FrontEnd/StructureTests.cs new file mode 100644 index 00000000..a9eb130c --- /dev/null +++ b/tests/HydraScript.Tests/Unit/FrontEnd/StructureTests.cs @@ -0,0 +1,35 @@ +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.Impl; +using HydraScript.Domain.FrontEnd.Lexer.TokenTypes; +using HydraScript.Infrastructure; +using Moq; +using Xunit; + +namespace HydraScript.Tests.Unit.FrontEnd; + +public class StructureTests +{ + [Fact] + public void ToStringCorrectTest() + { + var tokenTypes = new List + { + new("MyToken"), + new("OneToSeven") + }; + var provider = new Mock(); + provider.Setup(x => x.GetTokenTypes()) + .Returns(tokenTypes); + var structure = new Structure(provider.Object); + + var expectedText = string.Join( + '\n', + [ + "MyToken", + "OneToSeven", + "EOP", + "ERROR" + ]); + Assert.Equal(expectedText, structure.ToString()); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/FrontEnd/TextCoordinateSystemComputerTests.cs b/tests/HydraScript.Tests/Unit/FrontEnd/TextCoordinateSystemComputerTests.cs new file mode 100644 index 00000000..dd82f858 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/FrontEnd/TextCoordinateSystemComputerTests.cs @@ -0,0 +1,47 @@ +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Lexer.Impl; +using Xunit; + +namespace HydraScript.Tests.Unit.FrontEnd; + +public class TextCoordinateSystemComputerTests +{ + private ITextCoordinateSystemComputer _sut = new TextCoordinateSystemComputer(); + + [Fact] + public void GetLines_NoNewLine_SingleIndexResult() + { + const string text = "let x = 0"; + var result = _sut.GetLines(text); + result.Should().BeEquivalentTo([text.Length + Environment.NewLine.Length - 1]); + } + + [Fact] + public void GetLines_HasNewLine_SingleIndexResult() + { + var text = "let x = 0" + Environment.NewLine; + var result = _sut.GetLines(text); + result.Should().BeEquivalentTo([text.Length - 1]); + } + + [Fact] + public void GetLines_HasNewLines_MultipleIndicesResult() + { + const string stmt1 = "let x = 0"; + const string stmt2 = "x = x + 1"; + const string stmt3 = """print("x")"""; + var text = stmt1 + + Environment.NewLine + + stmt2 + + Environment.NewLine + + stmt3 + + Environment.NewLine; + var result = _sut.GetLines(text); + result.Should().BeEquivalentTo( + [ + stmt1.Length + Environment.NewLine.Length - 1, + stmt1.Length + stmt2.Length + Environment.NewLine.Length * 2 - 1, + stmt1.Length + stmt2.Length + stmt3.Length + Environment.NewLine.Length * 3 - 1 + ]); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/FrontEnd/TopDownParserTests.cs b/tests/HydraScript.Tests/Unit/FrontEnd/TopDownParserTests.cs new file mode 100644 index 00000000..bd6bd31f --- /dev/null +++ b/tests/HydraScript.Tests/Unit/FrontEnd/TopDownParserTests.cs @@ -0,0 +1,27 @@ +using HydraScript.Domain.FrontEnd.Lexer.Impl; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Domain.FrontEnd.Parser.Impl; +using HydraScript.Infrastructure; +using HydraScript.Tests.TestData; +using Xunit; + +namespace HydraScript.Tests.Unit.FrontEnd; + +public class TopDownParserTests +{ + private readonly IParser _parser = new TopDownParser(new RegexLexer( + new Structure(new TokenTypesProvider()), + new TextCoordinateSystemComputer())); + + [Theory] + [ClassData(typeof(ParserSuccessTestData))] + public void ParserDoesNotThrowTest(string text) + { + var ex = Record.Exception(() => + { + // ReSharper disable once UnusedVariable + var ast = _parser.Parse(text); + }); + Assert.Null(ex); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/IR/AstNodeTests.cs b/tests/HydraScript.Tests/Unit/IR/AstNodeTests.cs new file mode 100644 index 00000000..79a07e88 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/IR/AstNodeTests.cs @@ -0,0 +1,34 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using Xunit; +using BlockStatement = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements.BlockStatement; +using FunctionDeclaration = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded.FunctionDeclaration; +using IdentifierReference = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions.IdentifierReference; +using LexicalDeclaration = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded.LexicalDeclaration; +using Literal = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions.Literal; +using ScriptBody = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.ScriptBody; +using TypeIdentValue = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.TypeIdentValue; + +namespace HydraScript.Tests.Unit.IR; + +public class AstNodeTests +{ + [Fact] + public void PrecedenceTest() + { + var lexicalDecl = new LexicalDeclaration(false); + List stmtItemList = [lexicalDecl]; + // ReSharper disable once UnusedVariable + var func = new FunctionDeclaration( + name: new IdentifierReference(name: Guid.NewGuid().ToString()), + new TypeIdentValue( + TypeId: new IdentifierReference( + name: Guid.NewGuid().ToString())), + arguments: [], + new BlockStatement(stmtItemList)); + + _ = new ScriptBody([func]); + + Assert.True(lexicalDecl.ChildOf()); + Assert.False(lexicalDecl.ChildOf()); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/IR/FunctionWithUndefinedReturnStorageTests.cs b/tests/HydraScript.Tests/Unit/IR/FunctionWithUndefinedReturnStorageTests.cs new file mode 100644 index 00000000..caebadad --- /dev/null +++ b/tests/HydraScript.Tests/Unit/IR/FunctionWithUndefinedReturnStorageTests.cs @@ -0,0 +1,41 @@ +using HydraScript.Application.StaticAnalysis; +using HydraScript.Application.StaticAnalysis.Impl; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.IR.Impl.Symbols; +using Moq; +using Xunit; +using BlockStatement = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements.BlockStatement; +using FunctionDeclaration = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded.FunctionDeclaration; +using IdentifierReference = HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions.IdentifierReference; + +namespace HydraScript.Tests.Unit.IR; + +public class FunctionWithUndefinedReturnStorageTests +{ + [Fact] + public void StorageIsEmptyAfterFlushTest() + { + const string functionName = nameof(functionName); + IFunctionWithUndefinedReturnStorage storage = new FunctionWithUndefinedReturnStorage(); + + var symbol = new FunctionSymbol( + id: functionName, + parameters: [], + "undefined", + isEmpty: false); + + var decl = new FunctionDeclaration( + name: new IdentifierReference(functionName), + returnTypeValue: Mock.Of(), + arguments: new List(), + new BlockStatement(new List())); + + storage.Save(symbol, decl); + + var declarations = storage.Flush(); + Assert.Contains(decl, declarations); + + Assert.Empty(storage.Flush()); + } +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/IR/SymbolTableTests.cs b/tests/HydraScript.Tests/Unit/IR/SymbolTableTests.cs new file mode 100644 index 00000000..737d8c6c --- /dev/null +++ b/tests/HydraScript.Tests/Unit/IR/SymbolTableTests.cs @@ -0,0 +1,28 @@ +using HydraScript.Domain.IR; +using HydraScript.Domain.IR.Impl; +using Moq; +using Xunit; + +namespace HydraScript.Tests.Unit.IR; + +public class SymbolTableTests +{ + [Fact] + public void FindSymbolTest() + { + const string id = "ident"; + var type = new Mock(id); + + var symbol = new Mock(); + symbol.Setup(s => s.Id).Returns(id); + symbol.Setup(s => s.Type).Returns(type.Object); + + var outerScope = new SymbolTable(); + var innerScope = new SymbolTable(); + outerScope.AddSymbol(symbol.Object); + innerScope.AddOpenScope(outerScope); + + Assert.NotNull(innerScope.FindSymbol(id)); + Assert.True(outerScope.ContainsSymbol(id)); + } +} \ No newline at end of file diff --git a/Interpreter.Tests/Unit/IR/Types/ObjectTypeTests.cs b/tests/HydraScript.Tests/Unit/IR/Types/ObjectTypeTests.cs similarity index 72% rename from Interpreter.Tests/Unit/IR/Types/ObjectTypeTests.cs rename to tests/HydraScript.Tests/Unit/IR/Types/ObjectTypeTests.cs index 73422c99..600e85a5 100644 --- a/Interpreter.Tests/Unit/IR/Types/ObjectTypeTests.cs +++ b/tests/HydraScript.Tests/Unit/IR/Types/ObjectTypeTests.cs @@ -1,7 +1,7 @@ -using Interpreter.Lib.IR.CheckSemantics.Types; +using HydraScript.Domain.IR.Types; using Xunit; -namespace Interpreter.Tests.Unit.IR.Types; +namespace HydraScript.Tests.Unit.IR.Types; public class ObjectTypeTests { @@ -50,7 +50,6 @@ public void RecursiveTypeReferenceResolvingTest() { var number = new Type("number"); var array = new ArrayType(new Type("self")); - var method = new FunctionType(number, new List { new("self") }); var nullable = new NullableType(new Type("self")); var linkedListType = new ObjectType( new List @@ -61,17 +60,15 @@ public void RecursiveTypeReferenceResolvingTest() new("next", new Type("self")) })), new("children", array), - new("parent", nullable), - new("compare", method) + new("parent", nullable) } ); - linkedListType.ResolveSelfReferences("self"); + linkedListType.ResolveReference(linkedListType, refId: "self"); - Assert.Equal(linkedListType, ((ObjectType)linkedListType["wrapped"])["next"]); + Assert.Equal(linkedListType, ((ObjectType)linkedListType["wrapped"]!)["next"]); Assert.Equal(linkedListType, array.Type); Assert.Equal(linkedListType, nullable.Type); - Assert.Equal(linkedListType, method.Arguments[0]); } [Fact] @@ -86,7 +83,10 @@ public void NonSpecifiedTypesVisitingTest() new("prop", new Type("number")) } ); - var ex = Record.Exception(() => objectType.ResolveSelfReferences("self")); + var ex = Record.Exception( + () => objectType.ResolveReference( + objectType, + refId: "self")); Assert.Null(ex); Assert.Equal(objectType["next"], objectType); } @@ -96,7 +96,6 @@ public void ObjectTypeToStringTest() { var number = new Type("number"); var array = new ArrayType(new Type("self")); - var method = new FunctionType(number, new List { new("self") }); var nullable = new NullableType(new Type("self")); var linkedListType = new ObjectType( new List @@ -107,12 +106,12 @@ public void ObjectTypeToStringTest() new("next", new Type("self")) })), new("children", array), - new("parent", nullable), - new("compare", method) + new("parent", nullable) } ); - - linkedListType.ResolveSelfReferences("self"); + + linkedListType.ResolveReference(linkedListType, refId: "self"); + Assert.Contains("@this", linkedListType.ToString()); } @@ -125,23 +124,17 @@ public void SerializationOfTypeWithRecursivePropertyTest() new("data", new Type("number")), new("next", new Type("self")) } - ) { Recursive = true }; - nodeType.ResolveSelfReferences("self"); + ); + nodeType.ResolveReference(nodeType, refId: "self"); var linkedListType = new ObjectType( new List { - new("head", nodeType), - new("append", new FunctionType( - new Type("void"), - new List { nodeType } - ) - ), - new("copy", new FunctionType(new Type("self"), Array.Empty())) + new("head", nodeType) } - ) { Recursive = true }; - linkedListType.ResolveSelfReferences("self"); + ); + linkedListType.ResolveReference(linkedListType, refId: "self"); - Assert.Contains("head: head;", linkedListType.ToString()); + Assert.Contains("next: next;", linkedListType.ToString()); } } \ No newline at end of file diff --git a/Interpreter.Tests/Unit/IR/Types/TypeTests.cs b/tests/HydraScript.Tests/Unit/IR/Types/TypeTests.cs similarity index 69% rename from Interpreter.Tests/Unit/IR/Types/TypeTests.cs rename to tests/HydraScript.Tests/Unit/IR/Types/TypeTests.cs index 1593de75..c504e2c6 100644 --- a/Interpreter.Tests/Unit/IR/Types/TypeTests.cs +++ b/tests/HydraScript.Tests/Unit/IR/Types/TypeTests.cs @@ -1,7 +1,8 @@ -using Interpreter.Lib.IR.CheckSemantics.Types; +using HydraScript.Application.StaticAnalysis.Impl; +using HydraScript.Domain.IR.Types; using Xunit; -namespace Interpreter.Tests.Unit.IR.Types; +namespace HydraScript.Tests.Unit.IR.Types; public class TypeTests { @@ -42,8 +43,9 @@ public void TypeWrappingTest() [Fact] public void DefaultValueTest() { - Assert.Null(TypeUtils.GetDefaultValue(new NullableType(new Any()))); - Assert.Null(TypeUtils.GetDefaultValue(new NullType())); - Assert.Null(TypeUtils.GetDefaultValue(new ObjectType(new List()))); + var calculator = new DefaultValueForTypeCalculator(); + Assert.Null(calculator.GetDefaultValueForType(new NullableType(new Any()))); + Assert.Null(calculator.GetDefaultValueForType(new NullType())); + Assert.Null(calculator.GetDefaultValueForType(new ObjectType(new List()))); } } \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs b/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs new file mode 100644 index 00000000..895b5b8c --- /dev/null +++ b/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs @@ -0,0 +1,13 @@ +using HydraScript.Infrastructure; +using Xunit; + +namespace HydraScript.Tests.Unit.Infrastructure; + +public class GeneratedRegexContainerTests +{ + [Fact] + public void GetRegex_Generated_ManualIsUpToDate() => + GeneratedRegexContainer.Pattern.Trim().Should().Be( + GeneratedRegexContainer.GetRegex().ToString(), + "because В атрибут GeneratedRegex не подставлена актуальная сгенерированная регулярка"); +} \ No newline at end of file diff --git a/tests/HydraScript.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs b/tests/HydraScript.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs new file mode 100644 index 00000000..e620c6e3 --- /dev/null +++ b/tests/HydraScript.Tests/Unit/Infrastructure/LoggingEntitiesTests.cs @@ -0,0 +1,78 @@ +using System.IO.Abstractions; +using HydraScript.Domain.FrontEnd.Lexer; +using HydraScript.Domain.FrontEnd.Parser; +using HydraScript.Infrastructure; +using HydraScript.Infrastructure.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace HydraScript.Tests.Unit.Infrastructure; + +public class LoggingEntitiesTests +{ + private readonly Mock _file; + private readonly Mock _fileSystem; + + public LoggingEntitiesTests() + { + _file = new Mock(); + + _fileSystem = new Mock(); + _fileSystem.Setup(x => x.File) + .Returns(_file.Object); + } + + [Fact] + public void CorrectFileNameProducedByLexerTest() + { + var lexer = new Mock(); + lexer.Setup(x => x.GetTokens(It.IsAny())) + .Returns([]); + lexer.Setup(x => x.ToString()) + .Returns("lexer"); + + _file.Setup( + x => x.WriteAllText( + It.IsAny(), + It.IsAny())) + .Verifiable(); + + var loggingLexer = new LoggingLexer( + lexer.Object, + _fileSystem.Object, + inputFile: Options.Create(new InputFile { Info = new FileInfo("file") })); + loggingLexer.GetTokens(""); + + _file.Verify( + x => x.WriteAllText( + It.Is(p => p == "file.tokens"), + It.Is(c => c == "lexer")), + Times.Once()); + } + + [Fact] + public void CorrectTreeWrittenAndLoggingTreeProducedTest() + { + var ast = new Mock(); + ast.Setup(x => x.ToString()) + .Returns("digraph ast { }"); + + var parser = new Mock(); + parser.Setup(x => x.Parse(It.IsAny())) + .Returns(ast.Object); + + _file.Setup(x => x.WriteAllText( + It.IsAny(), It.IsAny() + )).Verifiable(); + + var loggingParser = new LoggingParser(parser.Object, _fileSystem.Object); + _ = loggingParser.Parse(""); + + _file.Verify( + x => x.WriteAllText( + It.Is(p => p == "ast.dot"), + It.Is(c => c == "digraph ast { }")), + Times.Once()); + } +} \ No newline at end of file