├── .clang-format ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── documentation-related.md │ └── feature-request.md └── workflows │ ├── darwin.yml │ ├── linux.yml │ └── windows.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.md ├── README.md ├── build.py ├── builder ├── __init__.py ├── cflags.py ├── compilerpool.py ├── constants.py ├── downloader.py ├── packaging.py ├── packaging │ ├── apt_install_deps.sh │ ├── debian_control.txt │ ├── debian_license.txt │ ├── kithare.desktop │ └── kithare_windows.iss ├── sdl_installer.py └── utils.py ├── deps └── README.txt ├── include └── kithare │ ├── core │ ├── ast.h │ ├── error.h │ ├── info.h │ ├── lexer.h │ ├── parser.h │ └── token.h │ └── lib │ ├── ansi.h │ ├── array.h │ ├── buffer.h │ ├── io.h │ └── string.h ├── makefile ├── misc ├── banner.png ├── icon.ico ├── logo.png ├── logo.py └── small.png └── src ├── ast.c ├── cli.c ├── error.c ├── lexer.c ├── mingw.rc ├── parser.c └── token.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: false 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: true 12 | AllowAllConstructorInitializersOnNextLine: true 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: Empty 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BreakBeforeBraces: Custom 28 | BraceWrapping: 29 | AfterCaseLabel: false 30 | AfterClass: false 31 | AfterControlStatement: false 32 | AfterEnum: false 33 | AfterFunction: false 34 | AfterNamespace: false 35 | AfterObjCDeclaration: false 36 | AfterStruct: false 37 | AfterUnion: false 38 | AfterExternBlock: false 39 | BeforeCatch: true 40 | BeforeElse: true 41 | IndentBraces: false 42 | SplitEmptyFunction: true 43 | SplitEmptyRecord: true 44 | SplitEmptyNamespace: true 45 | BreakBeforeBinaryOperators: None 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 104 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: false 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: true 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 4 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: false 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 2 93 | NamespaceIndentation: All 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Left 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: c++14 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 4 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report about a newly found bug 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | A clear and concise description of what the bug is. 12 | 13 | 14 | ## Reproduction 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | 22 | ## Expected Behavior 23 | A clear and concise description of what you expected to happen. 24 | 25 | 26 | ## Screenshots 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | 41 | ## Additional Context 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-related.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Related 3 | about: Report or propose a change to the documentation 4 | title: "[DOCS]" 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prologue 11 | A clear and concise introductory of what got you to make this issue. Eg. The documentation of ... isn't clear. 12 | 13 | 14 | ## Description 15 | A clear and concise description of what you want to happen. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea, feature, or a change to Kithare 4 | title: "[IDEA]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Prologue 11 | A clear and concise introductory of what got you to suggest this feature or change. Eg. I'm always frustrated when [...] 12 | 13 | 14 | ## Description 15 | A clear and concise description of what you want to happen. 16 | 17 | 18 | ## Alternatives 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | 22 | ## Additional Context 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/workflows/darwin.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - '.gitattributes' 9 | - '.gitignore' 10 | - 'LICENSE.md' 11 | - 'README.md' 12 | 13 | pull_request: 14 | branches: [ main ] 15 | paths-ignore: 16 | - 'docs/**' 17 | - '.gitattributes' 18 | - '.gitignore' 19 | - 'LICENSE.md' 20 | - 'README.md' 21 | 22 | jobs: 23 | build: 24 | name: ${{ matrix.arch }} 25 | runs-on: macos-latest 26 | 27 | strategy: 28 | fail-fast: false # if a particular matrix build fails, don't skip the rest 29 | matrix: 30 | arch: [x64] # TODO: arm64 and universal2 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - name: Install dependencies 36 | run: brew install sdl2 sdl2_image sdl2_mixer sdl2_net sdl2_ttf 37 | 38 | - name: Build source and tests 39 | run: python3 build.py --make installer 40 | 41 | - name: Run tests 42 | run: python3 build.py --make test 43 | 44 | - name: Upload Binaries 45 | uses: actions/upload-artifact@v3.0.0 46 | with: 47 | name: kithare-darwin-${{ matrix.arch }}-installers 48 | path: dist/packaging/ 49 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - '.gitattributes' 9 | - '.gitignore' 10 | - 'LICENSE.md' 11 | - 'README.md' 12 | 13 | pull_request: 14 | branches: [ main ] 15 | paths-ignore: 16 | - 'docs/**' 17 | - '.gitattributes' 18 | - '.gitignore' 19 | - 'LICENSE.md' 20 | - 'README.md' 21 | 22 | jobs: 23 | build: 24 | # some x86 deps have issues on 20.04, use Ubuntu 18.04 25 | name: Ubuntu (Bionic - 18.04) [${{ matrix.arch }}] 26 | runs-on: ubuntu-18.04 27 | 28 | strategy: 29 | fail-fast: false # if a particular matrix build fails, don't skip the rest 30 | matrix: 31 | arch: ['x64', 'x86'] 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - name: Install dependencies 37 | run: sudo bash ./builder/packaging/apt_install_deps.sh ${{ matrix.arch }} 38 | 39 | - name: Build sources, tests and make installer 40 | run: sudo python3 build.py --make installer --use-alien --arch ${{ matrix.arch }} 41 | 42 | - name: Install from .deb installer and test 43 | run: sudo apt-get install ./dist/packaging/*.deb 44 | 45 | - name: Run tests 46 | run: kcr # CKithare currently doesn't have unittests 47 | 48 | - name: Upload Binaries 49 | uses: actions/upload-artifact@v3.0.0 50 | with: 51 | name: kithare-linux-${{ matrix.arch }}-installers 52 | path: dist/packaging/ 53 | 54 | build-multiarch: 55 | name: ${{ matrix.os }} [${{ matrix.arch }}] 56 | runs-on: ubuntu-latest 57 | 58 | strategy: 59 | fail-fast: false # if a particular matrix build fails, don't skip the rest 60 | matrix: 61 | # TODO: Add fedora_latest, alpine_latest, archarm_latest 62 | include: 63 | - { arch: armv6, arch-alias: armv6, distro: buster, os: "Debian (Buster - 10)" } 64 | - { arch: armv7, arch-alias: armv7, distro: buster, os: "Debian (Buster - 10)" } 65 | - { arch: s390x, arch-alias: s390x, distro: buster, os: "Debian (Buster - 10)" } 66 | - { arch: arm64, arch-alias: aarch64, distro: buster, os: "Debian (Buster - 10)" } 67 | - { arch: ppc64le, arch-alias: ppc64le, distro: buster, os: "Debian (Buster - 10)" } 68 | 69 | steps: 70 | - uses: actions/checkout@v2 71 | 72 | - name: Build sources, tests and make installer 73 | uses: uraimo/run-on-arch-action@v2.0.5 74 | id: build 75 | with: 76 | arch: ${{ matrix.arch-alias }} 77 | distro: ${{ matrix.distro }} 78 | 79 | # Not required, but speeds up builds 80 | githubToken: ${{ github.token }} 81 | 82 | # Create an artifacts directory 83 | setup: mkdir -p ~/artifacts 84 | 85 | # Mount the artifacts directory as /artifacts in the container 86 | dockerRunArgs: --volume ~/artifacts:/artifacts 87 | 88 | # The shell to run commands with in the container 89 | shell: /bin/sh 90 | 91 | # Install some dependencies in the container. This speeds up builds if 92 | # you are also using githubToken. Any dependencies installed here will 93 | # be part of the container image that gets cached, so subsequent 94 | # builds don't have to re-install them. The image layer is cached 95 | # publicly in your project's package repository, so it is vital that 96 | # no secrets are present in the container state or logs. 97 | # remember to keep this updated from apt_install_deps, because that 98 | # cannot be called directly here 99 | install: | 100 | case "${{ matrix.distro }}" in 101 | ubuntu*|jessie|stretch|buster|bullseye) 102 | deps="alien build-essential" 103 | for dep in sdl2 sdl2-image sdl2-mixer sdl2-ttf sdl2-net; do 104 | deps="$deps lib${dep}-dev" 105 | done 106 | apt-get update --fix-missing 107 | apt-get upgrade -y 108 | apt-get install $deps -y 109 | ;; 110 | fedora*) 111 | # TODO 112 | dnf -y update 113 | ;; 114 | alpine*) 115 | # TODO 116 | apk update 117 | ;; 118 | esac 119 | 120 | # Produce a binary artifact, test it and place it in the mounted volume 121 | # Note: CKithare currently doesn't have unittests 122 | run: | 123 | echo "\nBuilding Kithare\n" 124 | python3 build.py --make installer --use-alien 125 | echo "\nInstalling from installer\n" 126 | apt-get install ./dist/packaging/*.deb 127 | echo "\nRunning tests\n" 128 | kcr 129 | cp ./dist/packaging/* /artifacts 130 | 131 | - name: Clean unneeded armv6 files 132 | if: matrix.arch == 'armv6' 133 | run: rm ~/artifacts/*.deb 134 | 135 | - name: Upload Binaries 136 | uses: actions/upload-artifact@v3.0.0 137 | with: 138 | name: kithare-linux-${{ matrix.arch }}-installers 139 | path: ~/artifacts 140 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - '.gitattributes' 9 | - '.gitignore' 10 | - 'LICENSE.md' 11 | - 'README.md' 12 | 13 | pull_request: 14 | branches: [ main ] 15 | paths-ignore: 16 | - 'docs/**' 17 | - '.gitattributes' 18 | - '.gitignore' 19 | - 'LICENSE.md' 20 | - 'README.md' 21 | 22 | jobs: 23 | build: 24 | name: ${{ matrix.arch }} 25 | runs-on: windows-latest 26 | 27 | strategy: 28 | fail-fast: false # if a particular matrix build fails, don't skip the rest 29 | matrix: 30 | arch: ['x64', 'x86'] 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - name: Build sources, tests and make installer 36 | run: python3 build.py --make installer --arch ${{ matrix.arch }} 37 | 38 | - name: Run tests 39 | run: python3 build.py --make test --arch ${{ matrix.arch }} 40 | 41 | # TODO: Test with installed kcr as well 42 | - name: Upload Binaries 43 | uses: actions/upload-artifact@v3.0.0 44 | with: 45 | name: kithare-windows-${{ matrix.arch }}-installers 46 | path: dist/packaging/ 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test files 2 | test.kh 3 | test.bat 4 | test.sh 5 | 6 | # Couple of IDEs' temporary/cache directory 7 | *~ 8 | *.swp 9 | .vs/ 10 | .vscode/* 11 | 12 | # Keep launch.json and tasks.json to use the C/C++ debugging tool from VS Code 13 | !.vscode/launch.json 14 | !.vscode/tasks.json 15 | 16 | # Executable/compiler generated files 17 | /build 18 | /deps/SDL/ 19 | /deps/AppImage/ 20 | /deps/mingw32/ 21 | /deps/mingw64/ 22 | /dist 23 | *.o 24 | *.obj 25 | *.exe 26 | *.out 27 | *.dll 28 | *.a 29 | *.lib 30 | icon.res 31 | 32 | # Python bytecode .pyc cache files 33 | __pycache__ 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // VS Code launch configuration to execute and debug kcr 2 | { 3 | "version": "2.0.0", 4 | "configurations": [ 5 | { 6 | "name": "Kithare Debugging", 7 | "type": "cppdbg", 8 | "request": "launch", 9 | "program": "${workspaceFolder}/dist/GCC-Debug/kcr", 10 | "preLaunchTask": "Kithare Debug Build", 11 | "args": [ 12 | "parse", 13 | "./test.kh" 14 | ], // If we have any args to pass to kcr, set it here 15 | "stopAtEntry": false, 16 | "cwd": "${workspaceFolder}", 17 | "environment": [], 18 | "externalConsole": false, 19 | "MIMode": "gdb", 20 | "setupCommands": [ 21 | { 22 | "description": "Enable pretty-printing for gdb", 23 | "text": "-enable-pretty-printing", 24 | "ignoreFailures": true 25 | } 26 | ], 27 | // Windows specific overrides 28 | "windows": { 29 | "name": "Kithare Debugging", 30 | "type": "cppdbg", 31 | "request": "launch", 32 | // With .exe extension 33 | "program": "${workspaceFolder}/dist/MinGW-Debug/kcr.exe", 34 | }, 35 | // Mac specific overrides 36 | "osx": { 37 | "name": "Kithare Debugging", 38 | "type": "cppdbg", 39 | "request": "launch", 40 | "program": "${workspaceFolder}/dist/GCC-Debug/kcr", 41 | "MIMode": "lldb", // Use lldb on Mac 42 | }, 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // VS Code task to compile a kcr build, either a debug or release build 2 | { 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "label": "Kithare Debug Build", 7 | "command": "python3 build.py --make debug", 8 | "windows": { 9 | "command": "py -3 build.py --make debug" 10 | }, 11 | "type": "shell", 12 | "problemMatcher": [ 13 | "$tsc" 14 | ], 15 | "presentation": { 16 | "reveal": "always" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "label": "Kithare Release Build", 25 | "command": "python3 build.py --make installer", 26 | "windows": { 27 | "command": "py -3 build.py --make installer" 28 | }, 29 | "type": "shell", 30 | "problemMatcher": [ 31 | "$tsc" 32 | ], 33 | "presentation": { 34 | "reveal": "always" 35 | }, 36 | "group": "build" 37 | }, 38 | { 39 | "label": "Kithare Unittests", 40 | "command": "python3 build.py --make test", 41 | "windows": { 42 | "command": "python build.py --make test" 43 | }, 44 | "type": "shell", 45 | "problemMatcher": [ 46 | "$tsc" 47 | ], 48 | "presentation": { 49 | "reveal": "always" 50 | }, 51 | "group": "test" 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright © 2021-present Kithare Organization 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT HAS BEEN ABANDONED. SOME GITHUB WORKFLOW LINKS MIGHT NOT WORK 2 | 3 | ![Kithare](misc/banner.png) 4 | 5 | # Kithare 6 | 7 | A statically-typed semi-safe programmming language which is inspired by [Python](https://www.python.org) and C++. Its syntax is designed to be easily-learnable by intermeddiate programmers and non-verbose. It offers functional style programming and object-oriented programming with polymorphism, function/method overloading, operator overloading, and templates. Join the [Discord](https://discord.com/invite/hXvY8CzS7A) server to keep track of the development or to ask for help. 8 | 9 | ## Supported OSes 10 | 11 | - **Windows** is well-supported by Kithare; both Windows 10 and Windows 11. Although not tested, Kithare should work well on Windows 7 and above, too. Both 32-bit and 64-bit variants are supported on Windows. However, Windows on ARM architecture is not supported yet. 12 | - **GNU/Linux** is also well-supported by Kithare; primarily Debian and Ubuntu-based modern sufficient Linux distros. Kithare supports a range of architectures (x86, x64, armv6, armv7, arm64, s390x and ppc64le). 13 | - **MacOS (Darwin)** machines whose version is 10.9 or above are supported. Kithare supports x86_64 (Intel) architecture and arm64 (Apple Silicon) architecture. In the future, Kithare will also support "universal" binaries, ones that works on both architectures. These univeral builds are "fat" builds, one that contains the binaries of both architectures in one binary. The usage of the "universal" builds are recommended over the usage of the architecture specific builds. 14 | 15 | ## Installation and Versioning 16 | 17 | - **Important note**: The language is still unfinished and there are no stable releases yet. 18 | - Kithare follows semantic versioning system for its releases. 19 | - All releases will be live on the [GitHub releases tab](https://github.com/avaxar/Kithare/releases). 20 | - For people who like to live on the edge, Kithare provides nightly builds. Here are some direct download links: 21 | 22 | 1. **Windows builds:** 23 | 24 | Each of these contain a portable ZIP-file based install, and an exe installer. 25 | - [32-bit](https://nightly.link/avaxar/Kithare/workflows/windows/main/kithare-windows-x86-installers.zip) 26 | - [64-bit](https://nightly.link/avaxar/Kithare/workflows/windows/main/kithare-windows-x64-installers.zip) 27 | 28 | 2. **MacOS (Darwin) builds:** 29 | 30 | Each of these contain a portable ZIP-file based install (more installers are WIP). 31 | - [x86_64 (Intel)](https://nightly.link/avaxar/Kithare/workflows/darwin/main/kithare-darwin-x64-installers.zip) 32 | - [arm64 (Apple Silicon - not implemented yet)](https://nightly.link/avaxar/Kithare/workflows/darwin/main/kithare-darwin-arm64-installers.zip) 33 | - [universal2 (not implemented yet)](https://nightly.link/avaxar/Kithare/workflows/darwin/main/kithare-darwin-universal2-installers.zip) 34 | 35 | 3. **Linux builds:** 36 | 37 | Each of these contain a portable ZIP-file based install, a `deb` apt package, an `rpm` package and an `AppImage` installer. 38 | - [x86_64 (64 bit Intel/AMD)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-x64-installers.zip) 39 | - [x86 (32 bit Intel/AMD)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-x86-installers.zip) 40 | - [armv6 (armhf - Common in older Raspberry Pis)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-armv6-installers.zip) 41 | - [armv7 (armhf - Common in newer Raspberry Pis)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-armv7-installers.zip) 42 | - [arm64 (aarch64)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-arm64-installers.zip) 43 | - [s390x (not well supported/tested)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-s390x-installers.zip) 44 | - [ppc64le (not well supported/tested)](https://nightly.link/avaxar/Kithare/workflows/linux/main/kithare-linux-ppc64le-installers.zip) 45 | 46 | - For these builds, the version in the installer packages is usually given by `YYYY.MM.DD.HHmm-nightly`. So if the date during the build is 22nd August 2021 and the time is 09:10:36 UTC, then the nightly build version will look like `2021.08.22.0910-nightly`. 47 | 48 | ## Building 49 | 50 | - Kithare uses a Python helper script to make building easier. So in order to build Kithare, you need Python v3.6 or above installed. 51 | - For advanced usage, you may checkout the `build.py` file, which contains instructions on how to use the build script to achieve more things. 52 | - A basic HOWTO to building Kithare is given below. 53 | 54 | ### Setup 55 | 56 | #### Windows 57 | 58 | - If you are building on Windows, the build script will automatically download and configure the deps. Kithare uses the MinGW compiler. If MinGW is not pre-installed, the buildscript will install it in a sub-directory. This means that on Windows, virtually no pre-build setup is required! 59 | 60 | #### Others 61 | 62 | - On other platforms, you would need the GCC compiler installed (On Mac, GCC is just a shim for clang). 63 | - Install the development libraries for these: `SDL2`, `SDL2_mixer`, `SDL2_image`, `SDL2_ttf`, `SDL2_net`. You may use your distro's package manager to do this. 64 | - A recommended way to do this on Mac is to use Homebrew. Just run `brew install sdl2 sdl2_image sdl2_mixer sdl2_net sdl2_ttf`. 65 | - On Ubuntu and other debian based systems, you can do `sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libsdl2-net-dev -y` 66 | 67 | ### Build 68 | 69 | - Run `python3 build.py` to build the project. If you are a `make` user, there is a stub `makefile` that calls the Python builder. 70 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | build.py 8 | Builder script to build Kithare. 9 | 10 | On Windows and MinGW: 11 | If you are building on Windows, the build script will automatically 12 | download and configure the deps. Kithare uses the MinGW compiler, 13 | if MinGW is not pre-installed, the buildscript will install it in a local 14 | directory. This means that on Windows, virtually no pre-build setup is 15 | required, just run this file with 'py build.py'. 16 | 17 | On other OS: 18 | This assumes you have GCC installed. Also, you need to install SDL 19 | dependencies on your own, via your systems package manager. 20 | Kithare needs 'SDL2', 'SDL2_mixer', 'SDL2_image', 'SDL2_ttf' and 21 | 'SDL2_net'. 22 | Make sure to install 'devel' releases of those, not just runtime shared 23 | libraries. 24 | 25 | A recommended and easy way to do this on MacOS, is via homebrew. Just run 26 | `brew install sdl2 sdl2_image sdl2_mixer sdl2_net sdl2_ttf`. 27 | 28 | And the build is really simple, just run 'python3 build.py' 29 | 30 | If you are on a 64-bit system, and want to compile for 32-bit architecture, 31 | pass '--arch x86' as an argument to the build script (note that this might not 32 | work in some cases) 33 | 34 | By default, the builder uses all cores on the machine to build Kithare. But if 35 | you want the builder to consume less CPU power while compiling (at the cost of 36 | longer compile times), you can use the '-j' flag to set the number of cores you 37 | want the builder to use. '-j1' means that you want to use only one core, '-j4' 38 | means that you want to use 4 cores, and so on. 39 | 40 | To just run tests, do 'python3 build.py --make test'. Note that this command is 41 | only going to run the tests, it does not do anything else. 42 | 43 | 'python3 build.py --clean {action}' can be used to clean generated files or 44 | folders, where action can be: 45 | - dep: Cleans installed dependencies 46 | - build: Cleans the 'build' dir 47 | - dist: Cleans the 'dist' dir excluding packages 48 | - package: Cleans the 'dist' of pakcages 49 | Or any combination of the above, like 'build+dist+package' or 'dep+build' 50 | 'python3 build.py --clean all' is a shorthand for cleaning 51 | 'dep+build+dist+package' 52 | 53 | In normal usage one need not run clean commands, but these are provided by the 54 | script anyways if you know what you are doing. 55 | 56 | To generate installers for Kithare, one can pass '--make installer' flag to 57 | this build script. On Windows, this will use INNO Setup to make an exe 58 | installer (INNO will be downloaded by the builder if not found). On Debian 59 | linux (and derived distros), the builder makes a .deb installer using dpkg-deb. 60 | 61 | Additionally on linux distros, one can pass a '--use-alien' flag, this will 62 | make the builder use the 'alien' package to generate a package for another 63 | distro from the package generated for the host distro. This feature is still 64 | considered an alpha-quality feature though. 65 | 66 | To pass any additional compiler flags, one can use CFLAGS, CPPFLAGS, CXXFLAGS, 67 | LDFLAGS and LDLIBS (makefile terminology) and additionally CCFLAGS (for unified 68 | C and C++ compilation flags). These can be set into env variables which the 69 | builder script will load from 70 | """ 71 | 72 | import sys 73 | 74 | from builder import BuildError, KithareBuilder, EPILOG 75 | 76 | 77 | def main(): 78 | """ 79 | Invoke Kithare builder class with arguments and build Kithare 80 | """ 81 | err_code = 0 82 | try: 83 | kithare = KithareBuilder() 84 | kithare.build() 85 | 86 | except BuildError as err: 87 | err_code = err.ecode 88 | if err.emsg: 89 | print("BuildError:", err.emsg) 90 | 91 | except Exception: # pylint: disable=broad-except 92 | print( 93 | "Unknown exception occured! This is probably a bug in the build " 94 | "script itself. Report this bug to Kithare devs, along with the " 95 | f"whole buildlog.{EPILOG}" 96 | ) 97 | raise 98 | 99 | except KeyboardInterrupt: 100 | err_code = 1 101 | print("Compilation was terminated with Keyboard Interrupt") 102 | 103 | print(EPILOG) 104 | sys.exit(err_code) 105 | 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /builder/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/__init__.py 8 | Defines the main KithareBuilder class that builds Kithare 9 | """ 10 | 11 | 12 | import platform 13 | import shutil 14 | import sys 15 | import time 16 | from pathlib import Path 17 | from typing import Optional 18 | 19 | from .cflags import CompilerFlags 20 | from .compilerpool import CompilerPool 21 | from .constants import ( 22 | C_STD_FLAG, 23 | COMPILER, 24 | CPP_STD_FLAG, 25 | CPU_COUNT, 26 | EPILOG, 27 | EXE, 28 | ICO_RES, 29 | INCLUDE_DIRNAME, 30 | INIT_TEXT, 31 | SUPPORTED_ARCHS, 32 | ) 33 | from .downloader import install_mingw 34 | from .packaging import get_packager 35 | from .sdl_installer import get_installer 36 | from .utils import ( 37 | BuildError, 38 | ConvertType, 39 | convert_machine, 40 | copy, 41 | get_machine, 42 | get_rel_path, 43 | parse_args, 44 | rmtree, 45 | run_cmd, 46 | should_build, 47 | ) 48 | 49 | __all__ = ["EPILOG", "KithareBuilder", "BuildError"] 50 | 51 | 52 | class KithareBuilder: 53 | """ 54 | Kithare builder class 55 | """ 56 | 57 | def __init__(self): 58 | """ 59 | Initialise kithare builder 60 | """ 61 | self.basepath = get_rel_path(Path(__file__).resolve().parents[1]) 62 | 63 | is_32_bit, args = parse_args() 64 | machine = get_machine(is_32_bit) 65 | dirname = ( 66 | f"{COMPILER}-Debug" if args.make == "debug" else f"{COMPILER}-{machine}" 67 | ) 68 | 69 | self.builddir = self.basepath / "build" / dirname 70 | self.exepath = self.basepath / "dist" / dirname / EXE 71 | 72 | self.sdl_installer = get_installer(self.basepath, self.exepath.parent, machine) 73 | self.installer = get_packager( 74 | self.basepath, 75 | self.exepath, 76 | machine, 77 | args.release, 78 | args.use_alien, 79 | args.make == "installer", 80 | ) 81 | 82 | self._handle_make_and_clean(args.make, args.clean) 83 | self.j_flag: Optional[int] = args.j 84 | if self.j_flag is not None and self.j_flag <= 0: 85 | raise BuildError("The '-j' flag should be a positive integer") 86 | 87 | # compiler flags 88 | self.cflags = CompilerFlags(self.basepath) 89 | 90 | self.t_0 = time.perf_counter() 91 | self.configure_compiler_and_flags(machine, is_32_bit, args.make) 92 | 93 | def _handle_make_and_clean(self, arg: Optional[str], clean: Optional[str]): 94 | """ 95 | Utility method to handle the first argument 96 | """ 97 | if arg == "test": 98 | # # CKithare currently doesn't have unittests 99 | # sys.exit(run_cmd(self.exepath, "--test")) 100 | sys.exit(run_cmd(self.exepath)) 101 | 102 | if clean is not None: 103 | clean_list = ( 104 | ["dep", "build", "dist", "package"] 105 | if clean == "all" 106 | else clean.split("+") 107 | ) 108 | cleaned = 0 109 | if "dep" in clean_list: 110 | cleaned += 1 111 | deps_dir = self.basepath / "deps" 112 | for dirname in deps_dir.iterdir(): 113 | rmtree(dirname) 114 | 115 | if "build" in clean_list: 116 | cleaned += 1 117 | rmtree(self.builddir.parent) 118 | 119 | dist_dir = self.exepath.parents[1] 120 | if "dist" in clean_list: 121 | cleaned += 1 122 | 123 | if (dist_dir / "packaging").is_dir(): 124 | for sub in dist_dir.iterdir(): 125 | if sub.is_dir() and sub.name != "packaging": 126 | rmtree(sub) 127 | else: 128 | rmtree(dist_dir) 129 | 130 | if "package" in clean_list: 131 | cleaned += 1 132 | rmtree(dist_dir / "packaging") 133 | 134 | # remove dist dir if it is empty 135 | try: 136 | dist_dir.rmdir() 137 | except OSError: 138 | pass 139 | 140 | if cleaned != len(clean_list): 141 | raise BuildError( 142 | "Invalid 'clean' arg passed. Use --help to check the correct usage" 143 | ) 144 | 145 | sys.exit(0) 146 | 147 | def configure_compiler_and_flags(self, machine: str, is_32_bit: bool, make: str): 148 | """ 149 | Configure and initialise self.cflags object, setup the MinGW compiler 150 | if it is missing 151 | """ 152 | if COMPILER == "MinGW": 153 | mingw_machine = convert_machine(machine, ConvertType.WINDOWS_MINGW) 154 | self.cflags.cc = f"{mingw_machine}-gcc.exe" 155 | self.cflags.cxx = f"{mingw_machine}-g++.exe" 156 | 157 | print(INIT_TEXT) 158 | if shutil.which(self.cflags.get_compiler()) is None: 159 | # Compiler is not installed and/or not on PATH 160 | if COMPILER == "MinGW": 161 | retpath = install_mingw(self.basepath, is_32_bit) 162 | else: 163 | raise BuildError(f"'{self.cflags.get_compiler()}' compiler missing!") 164 | 165 | self.cflags.cc = retpath / self.cflags.cc 166 | self.cflags.cxx = retpath / self.cflags.cxx 167 | self.cflags.windres = retpath / "windres.exe" 168 | 169 | print("Building Kithare...") 170 | print("Platform:", platform.platform()) 171 | print("Compiler:", COMPILER) 172 | print("Builder Python version:", platform.python_version()) 173 | 174 | if machine in SUPPORTED_ARCHS: 175 | print("Machine:", machine) 176 | 177 | else: 178 | print( 179 | "BuildWarning: Your CPU arch has been determined to be " 180 | f"'{machine}'\nNote that this is not well supported." 181 | ) 182 | 183 | print("Additional compiler info:") 184 | run_cmd(self.cflags.get_compiler(), "--version", strict=True, silent_cmds=True) 185 | 186 | self.cflags.ccflags.extend( 187 | ( 188 | "-Wall", 189 | "-pthread", 190 | "-g" if make == "debug" else "-O3", # no -O3 on debug mode 191 | self.basepath / INCLUDE_DIRNAME, 192 | ) 193 | ) 194 | 195 | if make != "debug": 196 | # don't pass -Werror in debug mode 197 | self.cflags.ccflags.append("-Werror") 198 | 199 | if COMPILER == "MinGW": 200 | self.cflags.add_m_flags("-municode", "-mthreads") 201 | 202 | # statically link C/C++ stdlib and winpthread on Windows MinGW 203 | self.cflags.ldflags.extend( 204 | ( 205 | "-static-libgcc", 206 | "-static-libstdc++", 207 | "-Wl,-Bstatic,--whole-archive", 208 | "-lwinpthread", 209 | "-Wl,--no-whole-archive", 210 | ) 211 | ) 212 | 213 | elif platform.system() == "Darwin": 214 | self.cflags.add_m_flags("-mmacosx-version-min=10.9") 215 | 216 | if is_32_bit: 217 | self.cflags.add_m_flags("-m32") 218 | 219 | self.cflags.cflags.append(C_STD_FLAG) 220 | self.cflags.cxxflags.append(CPP_STD_FLAG) 221 | 222 | self.cflags.load_from_env() 223 | 224 | def build_sources(self, build_skippable: bool): 225 | """ 226 | Generate obj files from source files, returns a list of generated 227 | objfiles. May also return None if all older objfiles are up date and 228 | dist exe already exists. 229 | """ 230 | skipped_files: list[Path] = [] 231 | objfiles: list[Path] = [] 232 | 233 | print("Building Kithare sources...") 234 | compilerpool = CompilerPool(self.j_flag, self.cflags) 235 | 236 | print(f"Building on {min(compilerpool.maxpoolsize, CPU_COUNT)} core(s)") 237 | if compilerpool.maxpoolsize > CPU_COUNT: 238 | print(f"Using {compilerpool.maxpoolsize} subprocess(es)") 239 | 240 | print() # newline 241 | for file in self.basepath.glob("src/**/*.c*"): 242 | if file.suffix not in {".c", ".cpp"}: 243 | # not a C or CPP file 244 | continue 245 | 246 | ofile = self.builddir / f"{file.stem}.o" 247 | if ofile in objfiles: 248 | raise BuildError("Got duplicate filename in Kithare source") 249 | 250 | objfiles.append(ofile) 251 | if build_skippable and not should_build( 252 | file, ofile, self.basepath / INCLUDE_DIRNAME 253 | ): 254 | # file is already built, skip it 255 | skipped_files.append(file) 256 | continue 257 | 258 | compilerpool.add(file, ofile) 259 | 260 | # wait for all sources to compile 261 | compilerpool.wait() 262 | if skipped_files: 263 | if len(skipped_files) == 1: 264 | print(f"Skipping file {skipped_files[0]}") 265 | print("Because the intermediate object file is already built\n") 266 | else: 267 | print("Skipping files:") 268 | print(*skipped_files, sep="\n") 269 | print("Because the intermediate object files are already built\n") 270 | 271 | if compilerpool.failed: 272 | raise BuildError( 273 | "Skipped building executable, because all files didn't build" 274 | ) 275 | 276 | if not objfiles: 277 | raise BuildError( 278 | "Failed to generate executable because no sources were found" 279 | ) 280 | 281 | if ( 282 | len(objfiles) == len(skipped_files) 283 | and self.exepath.is_file() 284 | and ( 285 | platform.system() != "Linux" 286 | or self.exepath.with_name(f"{EXE}-static").is_file() 287 | ) 288 | ): 289 | # exe(s) is(are) already up to date 290 | return None 291 | 292 | return objfiles 293 | 294 | def build_exe(self): 295 | """ 296 | Generate final exe. 297 | """ 298 | # load old cflags from the previous build 299 | build_conf = self.builddir / "build_conf.json" 300 | old_cflags = CompilerFlags.from_json(self.basepath, build_conf) 301 | 302 | # because order of args should not matter here 303 | build_skippable = self.cflags == old_cflags 304 | if not build_skippable: 305 | # update conf file with latest cflags 306 | self.cflags.to_json(build_conf) 307 | 308 | objfiles = self.build_sources(build_skippable) 309 | if objfiles is None: 310 | print("Skipping final exe(s) build, since it is already built") 311 | return 312 | 313 | # Handle exe icon on MinGW 314 | ico_res = self.basepath / ICO_RES 315 | if COMPILER == "MinGW": 316 | assetfile = self.basepath / "src" / "mingw.rc" 317 | 318 | print("Running windres command to set icon for exe") 319 | ret = run_cmd(self.cflags.windres, assetfile, "-O", "coff", "-o", ico_res) 320 | if ret: 321 | print("This means the final exe will not have the kithare logo") 322 | else: 323 | objfiles.append(ico_res) 324 | 325 | print() # newline 326 | 327 | print("Building executable") 328 | try: 329 | run_cmd( 330 | self.cflags.get_compiler(), 331 | "-o", 332 | self.exepath, 333 | *objfiles, 334 | *self.cflags.flags_by_ext("o"), 335 | strict=True, 336 | ) 337 | finally: 338 | # delete icon file 339 | if ico_res.is_file(): 340 | ico_res.unlink() 341 | 342 | if platform.system() == "Linux": 343 | # make statically linked binaries on Linux (useful for appimage) 344 | self.cflags.ldflags.append("-static") 345 | print() 346 | run_cmd( 347 | self.cflags.get_compiler(), 348 | "-o", 349 | self.exepath.with_name(f"{EXE}-static"), 350 | *objfiles, 351 | *self.cflags.flags_by_ext("o"), 352 | strict=True, 353 | ) 354 | 355 | # copy LICENSE.md and readme to dist 356 | for filename in ("LICENSE.md", "README.md"): 357 | copy(self.basepath / filename, self.exepath.parent) 358 | 359 | for dfile in self.exepath.parent.rglob("*"): 360 | # Make file permissions less strict on dist dir 361 | try: 362 | dfile.chmod(0o775) 363 | except OSError: 364 | raise BuildError( 365 | "Failed to set file permissions of files in dist dir" 366 | ) from None 367 | 368 | print("Kithare has been built successfully!") 369 | 370 | def build(self): 371 | """ 372 | Build Kithare 373 | """ 374 | # prepare directories 375 | self.builddir.mkdir(parents=True, exist_ok=True) 376 | self.exepath.parent.mkdir(parents=True, exist_ok=True) 377 | 378 | t_1 = time.perf_counter() 379 | # Prepare dependencies and cflags with SDL flags 380 | incflag = self.sdl_installer.install_all() 381 | if incflag is not None: 382 | self.cflags.ccflags.append(incflag) 383 | 384 | self.cflags.ldflags.extend(self.sdl_installer.ldflags) 385 | # do any pre-build setup for installer generation 386 | self.installer.setup() 387 | 388 | t_2 = time.perf_counter() 389 | self.build_exe() 390 | print(f"Path to executable: '{self.exepath}'\n") 391 | 392 | t_3 = time.perf_counter() 393 | # make installer if flag was passed already 394 | self.installer.package() 395 | 396 | t_4 = time.perf_counter() 397 | 398 | print("Done!\nSome timing stats for peeps who like to 'optimise':") 399 | 400 | # print MinGW install time only if it is large enough (haha majik number) 401 | if t_1 - self.t_0 > 0.69 and COMPILER == "MinGW": 402 | print(f"MinGW compiler took {t_1 - self.t_0:.3f} seconds to install") 403 | 404 | # print SDL install time stats only if it is large enough (haha majik number) 405 | if t_2 - t_1 > 0.69: 406 | print(f"SDL deps took {t_2 - t_1:.3f} seconds to configure and install") 407 | 408 | print(f"Kithare took {t_3 - t_2:.3f} seconds to compile") 409 | 410 | if t_4 - t_3 > 0.042: 411 | print(f"Generating the installer took {t_4 - t_3:.3f} seconds") 412 | -------------------------------------------------------------------------------- /builder/cflags.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/cflags.py 8 | Defines a CompilerFlags class for handling cflags 9 | """ 10 | 11 | 12 | import json 13 | import os 14 | from pathlib import Path 15 | from typing import Union 16 | 17 | 18 | class CompilerFlags: 19 | """ 20 | A CompilerFlags class 21 | """ 22 | 23 | def __init__(self, basepath: Path): 24 | """ 25 | Initialise CompilerFlags instance. This has attributes of individual 26 | compiler flags 27 | """ 28 | self.basepath = basepath 29 | 30 | # C Compiler name, gcc by default 31 | self.cc: Union[str, Path] = "gcc" 32 | 33 | # C++ Compiler name, g++ by default 34 | self.cxx: Union[str, Path] = "g++" 35 | 36 | # Windres command 37 | self.windres: Union[str, Path] = "windres" 38 | 39 | # flags for the C compiler, any Path object here is interpeted as -I 40 | self.cflags: list[Union[str, Path]] = [] 41 | 42 | # flags for the C++ compiler, any Path object here is interpeted as -I 43 | self.cxxflags: list[Union[str, Path]] = [] 44 | 45 | # flags for the C AND C++ compiler, any Path object here is interpeted as -I 46 | self.ccflags: list[Union[str, Path]] = [] 47 | 48 | # flags for the C PreProcessor 49 | self.cppflags: list[str] = [] 50 | 51 | # flags for the linker, any Path object here is interpeted as -L 52 | self.ldflags: list[Union[str, Path]] = [] 53 | 54 | def get_compiler(self, ext: str = "cpp"): 55 | """ 56 | Get compiler by file extension. Returns self.cc for C files and 57 | self.cxx for cpp files 58 | """ 59 | if ext.endswith("c"): 60 | return str(self.cc) 61 | 62 | if ext.endswith("cpp"): 63 | return str(self.cxx) 64 | 65 | raise RuntimeError(f"InternalError, Function 'get_compiler' got arg {ext}") 66 | 67 | @classmethod 68 | def from_json(cls, basepath: Path, jsondata: Union[Path, str]): 69 | """ 70 | Get CompilerFlags object from json dict representation 71 | """ 72 | if isinstance(jsondata, Path): 73 | try: 74 | jsondata = jsondata.read_text() 75 | except FileNotFoundError: 76 | jsondata = "null" 77 | 78 | try: 79 | obj = json.loads(jsondata) 80 | except json.JSONDecodeError: 81 | obj = None 82 | 83 | ret = cls(basepath) 84 | if isinstance(obj, list): 85 | # a list was the old way of storing CompilerFlags in json, now this 86 | # is merely kept for compatability 87 | ret.ccflags.extend(obj) 88 | 89 | elif isinstance(obj, dict): 90 | ret.from_dict(**obj) 91 | 92 | return ret 93 | 94 | def from_dict(self, **dictobj: Union[str, list]): 95 | """ 96 | Fill in attributes from a dict to the CompilerFlags object 97 | """ 98 | cc = dictobj.get("CC") 99 | if isinstance(cc, str): 100 | self.cc = cc 101 | 102 | cxx = dictobj.get("CXX") 103 | if isinstance(cxx, str): 104 | self.cxx = cxx 105 | 106 | for attr, key in ( 107 | (self.ccflags, "CCFLAGS"), 108 | (self.ldflags, "LDFLAGS"), 109 | (self.ldflags, "LDLIBS"), 110 | (self.cflags, "CFLAGS"), 111 | (self.cxxflags, "CXXFLAGS"), 112 | (self.cppflags, "CPPFLAGS"), 113 | ): 114 | try: 115 | obj = dictobj[key] 116 | except KeyError: 117 | pass 118 | else: 119 | if isinstance(obj, str): 120 | obj = obj.split() 121 | 122 | attr.extend(obj) 123 | 124 | def load_from_env(self): 125 | """ 126 | Load any CompilerFlags from environment variables 127 | """ 128 | self.from_dict(**os.environ) 129 | 130 | def resolve_paths( 131 | self, *flags: Union[str, Path], hflag: str = "-I", rel_base: bool = False 132 | ): 133 | """ 134 | Resolve path objects in a list, and return an iterator 135 | """ 136 | for flag in flags: 137 | if isinstance(flag, str): 138 | yield flag 139 | continue 140 | 141 | if rel_base: 142 | flag = flag.relative_to(self.basepath) 143 | 144 | yield hflag + str(flag) 145 | 146 | def __eq__(self, other: object): 147 | """ 148 | Compare two CompilerFlags objects 149 | """ 150 | if not isinstance(other, CompilerFlags): 151 | return NotImplemented 152 | 153 | cc = self.cc if isinstance(self.cc, str) else self.cc.relative_to(self.basepath) 154 | cxx = ( 155 | self.cxx 156 | if isinstance(self.cxx, str) 157 | else self.cxx.relative_to(self.basepath) 158 | ) 159 | 160 | occ = ( 161 | other.cc 162 | if isinstance(other.cc, str) 163 | else other.cc.relative_to(other.basepath) 164 | ) 165 | ocxx = ( 166 | other.cxx 167 | if isinstance(other.cxx, str) 168 | else other.cxx.relative_to(other.basepath) 169 | ) 170 | 171 | if str(cc) != str(occ) or str(cxx) != str(ocxx): 172 | return False 173 | 174 | for attr, other_attr in ( 175 | (self.cflags, other.cflags), 176 | (self.cxxflags, other.cxxflags), 177 | ): 178 | if sorted( 179 | self.resolve_paths(*attr, *self.ccflags, rel_base=True) 180 | ) != sorted(self.resolve_paths(*other_attr, *other.ccflags, rel_base=True)): 181 | return False 182 | 183 | if sorted( 184 | self.resolve_paths(*self.ldflags, hflag="-L", rel_base=True) 185 | ) != sorted(self.resolve_paths(*other.ldflags, hflag="-L", rel_base=True)): 186 | return False 187 | 188 | return sorted(self.cppflags) == sorted(other.cppflags) 189 | 190 | def to_json(self, file: Path): 191 | """ 192 | Write a file with a JSON dict representation of the CompilerFlags 193 | object 194 | """ 195 | cc = self.cc if isinstance(self.cc, str) else self.cc.relative_to(self.basepath) 196 | cxx = ( 197 | self.cxx 198 | if isinstance(self.cxx, str) 199 | else self.cxx.relative_to(self.basepath) 200 | ) 201 | 202 | file.write_text( 203 | json.dumps( 204 | { 205 | "CC": str(cc), 206 | "CXX": str(cxx), 207 | "CPPFLAGS": self.cppflags, 208 | "CCFLAGS": list(self.resolve_paths(*self.ccflags, rel_base=True)), 209 | "CFLAGS": list(self.resolve_paths(*self.cflags, rel_base=True)), 210 | "CXXFLAGS": list(self.resolve_paths(*self.cxxflags, rel_base=True)), 211 | "LDFLAGS": list( 212 | self.resolve_paths(*self.ldflags, hflag="-L", rel_base=True) 213 | ), 214 | }, 215 | indent=4, 216 | ) 217 | ) 218 | 219 | def flags_by_ext(self, ext: str): 220 | """ 221 | Get the appropriate compiler flags for compilation based on file 222 | extension. rel_base arg specifies whether the paths should be relative 223 | to base dir, or current working dir 224 | """ 225 | if ext.endswith("c"): 226 | yield from self.cppflags 227 | yield from self.resolve_paths(*self.ccflags) 228 | yield from self.resolve_paths(*self.cflags) 229 | 230 | if ext.endswith("cpp"): 231 | yield from self.cppflags 232 | yield from self.resolve_paths(*self.ccflags) 233 | yield from self.resolve_paths(*self.cxxflags) 234 | 235 | if ext.endswith("o"): 236 | yield from self.resolve_paths(*self.ldflags, hflag="-L") 237 | 238 | def add_m_flags(self, *mflags: str): 239 | """ 240 | Helper function to add -m flags to compiler flags. This updates both 241 | ccflags and ldflags 242 | """ 243 | self.ccflags.extend(mflags) 244 | self.ldflags.extend(mflags) 245 | -------------------------------------------------------------------------------- /builder/compilerpool.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/compilerpool.py 8 | Defines a CompilerPool class to compile multiple source files concurrently 9 | using a subprocess pool 10 | """ 11 | 12 | 13 | import subprocess 14 | import time 15 | from pathlib import Path 16 | from typing import Optional, Sequence 17 | 18 | from .cflags import CompilerFlags 19 | from .constants import CPU_COUNT 20 | from .utils import BuildError 21 | 22 | 23 | class CompilerPool: 24 | """ 25 | A pool of source files to be compiled in multiple subprocesses 26 | """ 27 | 28 | def __init__(self, maxpoolsize: Optional[int], cflags: CompilerFlags): 29 | """ 30 | Initialise CompilerPool instance. maxpoolsize is the limit on number of 31 | subprocesses that can be opened at a given point. If not specified 32 | (None), defaults to number of cores on the machine. The cflags arg is 33 | the compiler flags to be passed 34 | """ 35 | self.cflags = cflags 36 | 37 | self._procs: dict[Path, subprocess.Popen[str]] = {} 38 | self._queued_procs: list[tuple[Path, Path]] = [] 39 | self.failed: bool = False 40 | 41 | self.maxpoolsize = CPU_COUNT + 2 if maxpoolsize is None else maxpoolsize 42 | 43 | def _start_proc(self, cfile: Path, ofile: Path): 44 | """ 45 | Internal function to start a compile subprocess 46 | """ 47 | # pylint: disable=consider-using-with 48 | self._procs[cfile] = subprocess.Popen( 49 | ( 50 | self.cflags.get_compiler(cfile.suffix), 51 | "-o", 52 | str(ofile), 53 | "-c", 54 | str(cfile), 55 | *self.cflags.flags_by_ext(cfile.suffix), 56 | ), 57 | stdout=subprocess.PIPE, 58 | stderr=subprocess.STDOUT, 59 | universal_newlines=True, 60 | ) 61 | 62 | def _finish_proc(self, proc: subprocess.Popen): 63 | """ 64 | Take a finished Popen subprocess, print command name, stdout and 65 | error code 66 | """ 67 | # stderr is redirected to stdout here 68 | try: 69 | stdout, _ = proc.communicate() 70 | except ValueError: 71 | # can happen during a KeyboardInterrupt 72 | return 73 | 74 | if isinstance(proc.args, bytes): 75 | print(">", proc.args.decode()) 76 | elif isinstance(proc.args, Sequence) and not isinstance(proc.args, str): 77 | print(">", *proc.args) 78 | else: 79 | print(">", proc.args) 80 | 81 | if proc.returncode: 82 | print(stdout, end="") 83 | print(f"g++ exited with error code: {proc.returncode}\n") 84 | self.failed = True 85 | else: 86 | print(stdout) 87 | 88 | def update(self, start_new: bool = True): 89 | """ 90 | Runs an "update" operation. Any processes that have been finished are 91 | removed from the process pool after the subprocess output has been 92 | printed along with the return code (on error). Starts a new subprocess 93 | from the queued pending process pool. 94 | """ 95 | for cfile, proc in tuple(self._procs.items()): 96 | if proc.poll() is None: 97 | # proc is still running 98 | continue 99 | 100 | print(f"Building file: {cfile}") 101 | self._finish_proc(proc) 102 | self._procs.pop(cfile) 103 | 104 | # start a new process from queued process 105 | if self._queued_procs and start_new: 106 | self._start_proc(*self._queued_procs.pop()) 107 | 108 | def add(self, cfile: Path, ofile: Path): 109 | """ 110 | Add a source file to be compiled into the compiler pool. cfile is the 111 | Path object to the source file, while ofile is the Path object to the 112 | output file 113 | """ 114 | if len(self._procs) >= self.maxpoolsize: 115 | # pool is full, queue the command 116 | self._queued_procs.append((cfile, ofile)) 117 | else: 118 | self._start_proc(cfile, ofile) 119 | 120 | # call an update 121 | self.update() 122 | 123 | def poll(self): 124 | """ 125 | Returns False when all files in the pool finished compiling, True 126 | otherwise 127 | """ 128 | return bool(self._queued_procs or self._procs) 129 | 130 | def wait(self, timeout: int = 300): 131 | """ 132 | Block until all queued files are compiled. A timeout arg is used to 133 | kill builds if they take too much time. 134 | """ 135 | start_time = time.perf_counter() 136 | try: 137 | while self.poll(): 138 | self.update() 139 | time.sleep(0.005) 140 | 141 | if time.perf_counter() >= start_time + timeout: 142 | # hit timeout, quit wait 143 | raise BuildError(f"Hit build timeout of {timeout} s") 144 | 145 | finally: 146 | # gracefully terminate subprocesses on errors like KeyboardInterrupt 147 | self.update(start_new=False) 148 | for proc in self._procs.values(): 149 | proc.terminate() 150 | -------------------------------------------------------------------------------- /builder/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/constants.py 8 | A place to store a few constants used throughout the builder 9 | """ 10 | 11 | import os 12 | import platform 13 | 14 | INCLUDE_DIRNAME = "include" 15 | ICO_RES = "icon.res" 16 | 17 | # A set of architectures well supported by Kithare (have CI running tests on these) 18 | SUPPORTED_ARCHS = {"x86", "x64", "armv6", "armv7", "arm64", "ppc64le", "s390x"} 19 | 20 | C_STD_FLAG = "-std=gnu11" 21 | CPP_STD_FLAG = "-std=gnu++14" 22 | 23 | VERSION_PACKAGE_REV = "0" 24 | 25 | COMPILER = "MinGW" if platform.system() == "Windows" else "GCC" 26 | EXE = "kcr" 27 | if COMPILER == "MinGW": 28 | EXE += ".exe" 29 | 30 | _CPU_COUNT = os.cpu_count() 31 | CPU_COUNT = 1 if _CPU_COUNT is None else _CPU_COUNT 32 | 33 | INIT_TEXT = """Kithare Programming Language 34 | ---------------------------- 35 | An open source general purpose statically-typed cross-platform 36 | interpreted/transpiled C++/Python like programming language. 37 | 38 | The source code for Kithare programming language is distributed 39 | under the MIT license. 40 | Copyright (C) 2022 Kithare Organization 41 | 42 | GitHub: https://github.com/avaxar/Kithare 43 | Website: https://kithare.de/Kithare/ 44 | """ 45 | 46 | EPILOG = """ 47 | For any bug reports or feature requests, check out the issue tracker on GitHub 48 | https://github.com/avaxar/Kithare/issues 49 | """ 50 | -------------------------------------------------------------------------------- /builder/downloader.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/downloader.py 8 | Defines a ThreadedDownloader that helps in downloading dependencies in the 9 | background 10 | """ 11 | 12 | 13 | import hashlib 14 | import io 15 | import queue 16 | import ssl 17 | import threading 18 | import urllib.request as urllib 19 | import zipfile 20 | from pathlib import Path 21 | 22 | from .utils import BuildError 23 | 24 | # allow unverified SSL because armv7 CI errors at that for some reason while 25 | # downloading deps 26 | ssl._create_default_https_context = ssl._create_unverified_context 27 | 28 | # Downloads timeout in seconds 29 | DOWNLOAD_TIMEOUT = 600 30 | TIMEOUT_PER_LOOP = 0.05 31 | 32 | LINKS_AND_HASHES = { 33 | "SDL2": "0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1", 34 | "SDL2_image": "41d9e5ff98aa84cf66e6c63c78e7c346746982fa53d3f36633423cc9177f986c", 35 | "SDL2_mixer": "14250b2ade20866c7b17cf1a5a5e2c6f3920c443fa3744f45658c8af405c09f1", 36 | "SDL2_ttf": "c35aebf74448090e6413802cc55c461098a62ccdb6d74c0f77f6cf3569b72f3c", 37 | "SDL2_net": "fe0652ab1bdbeae277d7550f2ed686a37a5752f7a624f54f19cf1bd6ba5cb9ff", 38 | "MinGW32": "41fe7cde5f9dc470feed65dcbc46b4d9f8eb5ef1a9e204e2b5513359cee18b00", 39 | "MinGW64": "7e9f65a754fa5780a1bf0a4c26c42a63320a1f15376ba097b9da5463c9cfef77", 40 | "INNO Setup Installer": "50d21aab83579245f88e2632a61b943ad47557e42b0f02e6ce2afef4cdd8deb1", 41 | "AppImageTool": { 42 | "x86_64": "df3baf5ca5facbecfc2f3fa6713c29ab9cefa8fd8c1eac5d283b79cab33e4acb", 43 | "i686": "104978205c888cb2ad42d1799e03d4621cb9a6027cfb375d069b394a82ff15d1", 44 | "armhf": "36bb718f32002357375d77b082c264baba2a2dcf44ed1a27d51dbb528fbb60f6", 45 | "aarch64": "334e77beb67fc1e71856c29d5f3f324ca77b0fde7a840fdd14bd3b88c25c341f", 46 | }, 47 | "AppImageRun": { 48 | "x86_64": "fd0e2c14a135e7741ef82649558150f141a04c280ed77a5c6f9ec733627e520e", 49 | "i686": "ec319f2ed657729c160d492330b617709047195bf55507ff6e7264cb6761c66c", 50 | "armhf": "cf3c197b8ef4faf1a3a14f71f5d05a49be0cca0a4a8973d58e34c78e26d4ff80", 51 | "aarch64": "9214c4c1f7a3cdd77f8d558c2039230a322469f8aaf7c71453eeaf1f2f33d204", 52 | }, 53 | "AppImageRuntime": { 54 | "x86_64": "328e0d745c5c6817048c27bc3e8314871703f8f47ffa81a37cb06cd95a94b323", 55 | "i686": "5cbfd3c7e78d9ebb16b9620b28affcaa172f2166f1ef5fe7ef878699507bcd7f", 56 | "armhf": "c143d8981702b91cc693e5d31ddd91e8424fec5911fa2dda72082183b2523f47", 57 | "aarch64": "d2624ce8cc2c64ef76ba986166ad67f07110cdbf85112ace4f91611bc634c96a", 58 | }, 59 | } 60 | 61 | MINGW_DOWNLOAD_DIR = ( 62 | "https://github.com/brechtsanders/winlibs_mingw/releases/" 63 | "download/11.3.0-14.0.3-10.0.0-ucrt-r3/" 64 | ) 65 | MINGW_ZIP = ( 66 | "winlibs-x86_64-posix-seh-gcc-11.3.0-mingw-w64ucrt-10.0.0-r3.zip", 67 | "winlibs-i686-posix-dwarf-gcc-11.3.0-mingw-w64ucrt-10.0.0-r3.zip", 68 | ) 69 | 70 | 71 | class ThreadedDownloader: 72 | """ 73 | Install file(s) concurrently using threads in the background 74 | """ 75 | 76 | def __init__(self): 77 | """ 78 | Initialise ThreadedDownloader object 79 | """ 80 | self.threads: set[threading.Thread] = set() 81 | self.downloaded: queue.Queue[tuple[str, str, bytes]] = queue.Queue() 82 | 83 | def _download_thread(self, name: str, download_link: str, flavour: str): 84 | """ 85 | Download a file/resource on a seperate thread 86 | """ 87 | print(f"Downloading {name} from {download_link}") 88 | request = urllib.Request( 89 | download_link, 90 | headers={"User-Agent": "Chrome/35.0.1916.47 Safari/537.36"}, 91 | ) 92 | 93 | download: bytes = b"" 94 | try: 95 | with urllib.urlopen(request) as downloadobj: 96 | download = downloadobj.read() 97 | 98 | except OSError: 99 | # some networking error 100 | pass 101 | 102 | self.downloaded.put((name, flavour, download)) 103 | 104 | def download(self, name: str, download_link: str, flavour: str = ""): 105 | """ 106 | Download a file 107 | """ 108 | # Start thread, add it to a set of running threads 109 | thread = threading.Thread( 110 | target=self._download_thread, 111 | name=f"Installer Thread for {name} ({flavour})", 112 | args=(name, download_link, flavour), 113 | daemon=True, 114 | ) 115 | thread.start() 116 | self.threads.add(thread) 117 | 118 | def is_downloading(self): 119 | """ 120 | Check whether a file is being downloaded 121 | """ 122 | return any(t.is_alive() for t in self.threads) 123 | 124 | def get_finished(self): 125 | """ 126 | Iterate over downloaded resources (name-data pairs). Blocks while 127 | waiting for all threads to complete. data being None indicates error in 128 | download 129 | """ 130 | loops = 0 131 | while self.is_downloading() or not self.downloaded.empty(): 132 | try: 133 | # Do timeout and loop because we need be able to handle any 134 | # potential KeyboardInterrupt errors 135 | name, flavour, data = self.downloaded.get(timeout=TIMEOUT_PER_LOOP) 136 | except queue.Empty: 137 | pass 138 | 139 | else: 140 | if not data: 141 | raise BuildError( 142 | f"Failed to download {name} due to some networking error" 143 | ) 144 | 145 | datahash = hashlib.sha256(data).hexdigest() 146 | presethash = LINKS_AND_HASHES.get(name) 147 | if isinstance(presethash, dict): 148 | presethash = presethash.get(flavour) 149 | 150 | if datahash != presethash: 151 | print("Got hash:", datahash) 152 | if presethash is not None: 153 | raise BuildError( 154 | f"Download hash for {name} ({flavour}) mismatched" 155 | ) 156 | 157 | print( 158 | f"BuildWarning: Download {name} ({flavour}) has not " 159 | "been hash verified!" 160 | ) 161 | 162 | print(f"Successfully downloaded {name}!") 163 | yield name, data 164 | 165 | loops += 1 166 | if loops * TIMEOUT_PER_LOOP > DOWNLOAD_TIMEOUT: 167 | raise BuildError( 168 | f"Download(s) timed out! Took longer than {DOWNLOAD_TIMEOUT} s." 169 | ) 170 | 171 | def get_one(self): 172 | """ 173 | Get one downloaded resource (name-data pair). Function can block while 174 | waiting for resource. If download failed, raises error. 175 | """ 176 | for name, data in self.get_finished(): 177 | return name, data 178 | 179 | raise BuildError("Failed to fetch downloads as all were completed") 180 | 181 | 182 | def install_mingw(basepath: Path, is_32_bit: bool): 183 | """ 184 | Install MinGW into the deps folder, this is used as a fallback when MinGW 185 | is not pre-installed on the machine. Returns path object to MinGW bin dir 186 | """ 187 | mingw_name = "MinGW32" if is_32_bit else "MinGW64" 188 | deps_dir = basepath / "deps" 189 | ret = deps_dir / mingw_name.lower() / "bin" 190 | if ret.is_dir(): 191 | return ret 192 | 193 | print("MinGW is not pre-installed, installing it into deps dir.") 194 | downloader = ThreadedDownloader() 195 | downloader.download(mingw_name, MINGW_DOWNLOAD_DIR + MINGW_ZIP[is_32_bit]) 196 | print("This can take a while, depending on your internet speeds...") 197 | 198 | with zipfile.ZipFile(io.BytesIO(downloader.get_one()[1]), "r") as zipped: 199 | zipped.extractall(deps_dir) 200 | 201 | print() # newline 202 | return ret 203 | -------------------------------------------------------------------------------- /builder/packaging.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/packaging.py 8 | Defines classes to handle Kithare packaging into platform-specific installers 9 | """ 10 | 11 | import platform 12 | from pathlib import Path 13 | from typing import Optional 14 | from zipfile import ZipFile 15 | 16 | from .constants import EXE, VERSION_PACKAGE_REV 17 | from .downloader import ThreadedDownloader 18 | from .utils import BuildError, ConvertType, convert_machine, copy, rmtree, run_cmd 19 | 20 | # Windows INNO installer related constants, remember to keep updated 21 | INNO_SETUP_DOWNLOAD = "https://files.jrsoftware.org/is/6/innosetup-6.2.1.exe" 22 | INNO_FLAGS = "/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /NOCANCEL".split() 23 | 24 | INNO_COMPILER_PATH = Path("C:\\", "Program Files (x86)", "Inno Setup 6", "ISCC.exe") 25 | 26 | APPIMAGE_DOWNLOAD = "https://github.com/AppImage/AppImageKit/releases/latest/download" 27 | 28 | 29 | class DummyPackager: 30 | """ 31 | A packager object being an instance of this class indicates that no 32 | packages are actually being generated. Subclasses of this implement 33 | packager functionality. 34 | """ 35 | 36 | def setup(self): 37 | """ 38 | This function sets up any dependencies and such needed to make the 39 | package. If there is no setup to do, this function does nothing. 40 | """ 41 | 42 | def package(self): 43 | """ 44 | This function handles packaging. If there are no packages to make, this 45 | function does nothing. 46 | """ 47 | 48 | 49 | class Packager(DummyPackager): 50 | """ 51 | Packager is a base class for all other platform-specific Packager classes, 52 | that help packaging Kithare. 53 | """ 54 | 55 | def __init__(self, basepath: Path, exepath: Path, machine: str, version: str): 56 | """ 57 | Initialise Packager class 58 | """ 59 | self.paths = { 60 | "base": basepath, 61 | "packaging": basepath / "builder" / "packaging", 62 | "build": basepath / "build" / "packaging", 63 | "dist": basepath / "dist" / "packaging", 64 | "exe": exepath, 65 | } 66 | self.machine = machine 67 | self.version = version 68 | 69 | self.downloader: Optional[ThreadedDownloader] = None 70 | 71 | def package(self): 72 | """ 73 | Make portable zip package 74 | """ 75 | print("Making portable binary ZIP") 76 | zipname = ( 77 | f"kithare-{self.version}-{platform.system().lower()}-{self.machine}.zip" 78 | ) 79 | portable_zip = self.paths["dist"] / zipname 80 | 81 | portable_zip.parent.mkdir(exist_ok=True) 82 | with ZipFile(portable_zip, mode="w") as myzip: 83 | # copy executable and other files 84 | for dfile in self.paths["exe"].parent.rglob("*"): 85 | zipped_file = Path("Kithare") / dfile.relative_to( 86 | self.paths["exe"].parent 87 | ) 88 | myzip.write(dfile, arcname=zipped_file) 89 | 90 | print(f"Finished making zipfile in '{portable_zip}'\n") 91 | 92 | 93 | class WindowsPackager(Packager): 94 | """ 95 | Subclass of Packager that handles Windows packaging 96 | """ 97 | 98 | def __init__(self, basepath: Path, exepath: Path, machine: str, version: str): 99 | """ 100 | Initialise WindowsPackager class 101 | """ 102 | super().__init__(basepath, exepath, machine, version) 103 | 104 | self.machine = convert_machine(machine, ConvertType.WINDOWS) 105 | 106 | def setup(self): 107 | """ 108 | This prepares INNO installer 109 | """ 110 | if INNO_COMPILER_PATH.is_file(): 111 | return 112 | 113 | print("Could not find pre-installed INNO Setup, after looking in dir") 114 | print(INNO_COMPILER_PATH.parent) 115 | print( 116 | "Downloading and installing INNO Setup in the background while " 117 | "compilation continues\n" 118 | ) 119 | 120 | # Download INNO Setup installer in background 121 | self.downloader = ThreadedDownloader() 122 | self.downloader.download("INNO Setup Installer", INNO_SETUP_DOWNLOAD) 123 | 124 | def package(self): 125 | """ 126 | Make installer for Windows 127 | """ 128 | super().package() 129 | 130 | print("Using Windows installer configuration") 131 | 132 | rmtree(self.paths["build"]) # clean old build dir 133 | self.paths["build"].mkdir() 134 | 135 | default_iss_file = self.paths["packaging"] / "kithare_windows.iss" 136 | iss_file = self.paths["build"] / "kithare_windows.iss" 137 | 138 | # Rewrite iss file, with some macros defined 139 | iss_file.write_text( 140 | f'#define MyAppVersion "{self.version}"\n' 141 | + f'#define MyAppArch "{self.machine}"\n' 142 | + f'#define BasePath "{self.paths["base"].resolve()}"\n' 143 | + default_iss_file.read_text() 144 | ) 145 | 146 | if self.downloader is not None: 147 | if self.downloader.is_downloading(): 148 | print("Waiting for INNO Setup download to finish") 149 | 150 | inno_installer = self.paths["build"] / "innosetup_installer.exe" 151 | inno_installer.write_bytes(self.downloader.get_one()[1]) 152 | 153 | print("Installing Inno setup") 154 | try: 155 | run_cmd(inno_installer, *INNO_FLAGS, strict=True) 156 | finally: 157 | inno_installer.unlink() 158 | 159 | print() # newline 160 | 161 | print("Making Kithare installer") 162 | run_cmd(INNO_COMPILER_PATH, iss_file, strict=True) 163 | print("Successfully made Kithare installer!\n") 164 | 165 | 166 | class LinuxPackager(Packager): 167 | """ 168 | Subclass of Packager that handles Linux packaging 169 | """ 170 | 171 | def __init__( 172 | self, 173 | basepath: Path, 174 | exepath: Path, 175 | machine: str, 176 | version: str, 177 | use_alien: bool, 178 | ): 179 | """ 180 | Initialise LinuxPackager class 181 | """ 182 | super().__init__(basepath, exepath, machine, version) 183 | self.version += f"-{VERSION_PACKAGE_REV}" 184 | 185 | self.use_alien = use_alien 186 | self.appimagekitdir: Optional[Path] = None 187 | 188 | def setup(self): 189 | """ 190 | This prepares AppImageKit 191 | """ 192 | try: 193 | appimage_type = convert_machine(self.machine, ConvertType.APP_IMAGE) 194 | except BuildError as exc: 195 | print(f"Skipping AppImage generation, because {exc.emsg}\n") 196 | return 197 | 198 | self.appimagekitdir = self.paths["base"] / "deps" / "AppImage" / appimage_type 199 | 200 | if self.appimagekitdir.is_dir(): 201 | # AppImageKit already exists, no installation required 202 | return 203 | 204 | print( 205 | "Downloading and installing AppImageKit (for making AppImages) in " 206 | "the background while compilation continues" 207 | ) 208 | 209 | # Download INNO Setup installer in background 210 | self.downloader = ThreadedDownloader() 211 | self.downloader.download( 212 | "AppImageTool", 213 | f"{APPIMAGE_DOWNLOAD}/appimagetool-{appimage_type}.AppImage", 214 | appimage_type, 215 | ) 216 | self.downloader.download( 217 | "AppImageRun", 218 | f"{APPIMAGE_DOWNLOAD}/AppRun-{appimage_type}", 219 | appimage_type, 220 | ) 221 | self.downloader.download( 222 | "AppImageRuntime", 223 | f"{APPIMAGE_DOWNLOAD}/runtime-{appimage_type}", 224 | appimage_type, 225 | ) 226 | 227 | print() # newline 228 | 229 | def make_appimage(self): 230 | """ 231 | Make AppImage installers 232 | """ 233 | if self.appimagekitdir is None: 234 | return 235 | 236 | print("Making Linux universal packages with AppImage") 237 | 238 | installer_build_dir = self.paths["build"] / "kithare.AppDir" 239 | rmtree(installer_build_dir) # clean old build dir 240 | 241 | bin_dir = installer_build_dir / "usr" / "bin" 242 | bin_dir.mkdir(parents=True) 243 | 244 | # copy static dist exe 245 | copied_file = copy(self.paths["exe"].with_name(f"{EXE}-static"), bin_dir) 246 | copied_file.rename(copied_file.with_name(EXE)) 247 | 248 | # copy desktop file 249 | copy(self.paths["packaging"] / "kithare.desktop", installer_build_dir) 250 | 251 | # copy icon file 252 | copied_icon = copy( 253 | self.paths["base"] / "misc" / "small.png", installer_build_dir 254 | ) 255 | copied_icon.rename(copied_icon.with_name("kithare.png")) 256 | 257 | self.appimagekitdir.mkdir(parents=True, exist_ok=True) 258 | 259 | appimagekit = { 260 | "AppImageTool": self.appimagekitdir / "appimagetool.AppImage", 261 | "AppImageRun": self.appimagekitdir / "AppRun", 262 | "AppImageRuntime": self.appimagekitdir / "runtime", 263 | } 264 | 265 | if self.downloader is not None: 266 | if self.downloader.is_downloading(): 267 | print("Waiting for AppImageKit downloads to finish") 268 | 269 | for name, data in self.downloader.get_finished(): 270 | # save download in file 271 | appimagekit[name].write_bytes(data) 272 | appimagekit[name].chmod(0o775) 273 | 274 | if self.machine not in {"x86", "x64"}: 275 | # A workaround for AppImage bug on arm docker 276 | # https://github.com/AppImage/AppImageKit/issues/1056 277 | run_cmd( 278 | "sed", 279 | "-i", 280 | r"s|AI\x02|\x00\x00\x00|", 281 | appimagekit["AppImageTool"], 282 | strict=True, 283 | ) 284 | 285 | # copy main runfile to AppDir 286 | copy(appimagekit["AppImageRun"], installer_build_dir) 287 | 288 | dist_image = ( 289 | self.paths["dist"] 290 | / f"kithare-{self.version}-{self.appimagekitdir.name}.AppImage" 291 | ) 292 | dist_image.parent.mkdir(exist_ok=True) 293 | 294 | run_cmd( 295 | appimagekit["AppImageTool"], 296 | "--appimage-extract-and-run", 297 | "--runtime-file", 298 | appimagekit["AppImageRuntime"], 299 | installer_build_dir, 300 | dist_image, 301 | strict=True, 302 | ) 303 | dist_image.chmod(0o775) 304 | print(f"Successfully made AppImage installer in '{dist_image}'!\n") 305 | 306 | def debian_package(self): 307 | """ 308 | Make deb installer file for Debian 309 | """ 310 | print("\nMaking Debian .deb installer") 311 | 312 | machine = convert_machine(self.machine, ConvertType.LINUX_DEB) 313 | 314 | installer_dir = self.paths["build"] / "Debian" / f"kithare_{self.version}" 315 | rmtree(installer_dir.parent) # clean old dir 316 | 317 | bin_dir = installer_dir / "usr" / "bin" 318 | bin_dir.mkdir(parents=True) 319 | 320 | # copy dist exe 321 | copy(self.paths["exe"], bin_dir) 322 | 323 | # write a control file 324 | write_control_file = installer_dir / "DEBIAN" / "control" 325 | write_control_file.parent.mkdir() 326 | 327 | control_file = self.paths["packaging"] / "debian_control.txt" 328 | write_control_file.write_text( 329 | f"Version: {self.version}\n" 330 | + f"Architecture: {machine}\n" 331 | + control_file.read_text() 332 | ) 333 | 334 | # write license file in the doc dir 335 | doc_dir = installer_dir / "usr" / "share" / "doc" / "kithare" 336 | doc_dir.mkdir(parents=True) 337 | license_file = copy(self.paths["packaging"] / "debian_license.txt", doc_dir) 338 | license_file.rename(license_file.with_name("copyright")) 339 | 340 | run_cmd("dpkg-deb", "--build", installer_dir, self.paths["dist"], strict=True) 341 | print(".deb file was made successfully\n") 342 | 343 | if self.use_alien: 344 | print("Using 'alien' package to make rpm packages from debian packages") 345 | rpm_machine = convert_machine(self.machine, ConvertType.LINUX_RPM) 346 | run_cmd( 347 | "alien", 348 | "--to-rpm", 349 | "--keep-version", 350 | f"--target={rpm_machine}", 351 | self.paths["dist"] / f"kithare_{self.version}_{machine}.deb", 352 | strict=True, 353 | ) 354 | 355 | gen_rpm = ( 356 | self.paths["base"] 357 | / f"kithare-{self.version.replace('-', '_', 1)}.{rpm_machine}.rpm" 358 | ) 359 | try: 360 | rpm_file = copy(gen_rpm, self.paths["dist"]) 361 | finally: 362 | if gen_rpm.is_file(): 363 | gen_rpm.unlink() 364 | 365 | print(f"Generated rpm package in '{rpm_file}'!\n") 366 | 367 | def package(self): 368 | """ 369 | Make installer for Linux 370 | """ 371 | super().package() 372 | 373 | self.make_appimage() 374 | 375 | print("Using Linux installer configuration") 376 | print("Testing for Debian") 377 | try: 378 | run_cmd("dpkg-deb", "--version", strict=True) 379 | except BuildError: 380 | print("The platform is not Debian-based") 381 | else: 382 | return self.debian_package() 383 | 384 | raise BuildError( 385 | "Your linux distro is unsupported by Kithare for now. " 386 | "However, adding support for more distros is in our TODO!" 387 | ) 388 | 389 | 390 | class MacPackager(Packager): 391 | """ 392 | Subclass of Packager that handles MacOS packaging 393 | """ 394 | 395 | 396 | def get_packager( 397 | basepath: Path, 398 | exepath: Path, 399 | machine: str, 400 | version: str, 401 | use_alien: bool, 402 | make_installer: bool, 403 | ): 404 | """ 405 | Get appropriate packager class for handling packaging 406 | """ 407 | if not make_installer: 408 | if use_alien: 409 | raise BuildError( 410 | "The '--use-alien' flag cannot be passed when installer(s) " 411 | "are not being made!" 412 | ) 413 | 414 | return DummyPackager() 415 | 416 | system = platform.system() 417 | if system == "Windows": 418 | if use_alien: 419 | raise BuildError("'--use-alien' is not a supported flag on this OS") 420 | 421 | return WindowsPackager(basepath, exepath, machine, version) 422 | 423 | if system == "Linux": 424 | return LinuxPackager(basepath, exepath, machine, version, use_alien) 425 | 426 | if system == "Darwin": 427 | if use_alien: 428 | raise BuildError("'--use-alien' is not a supported flag on this OS") 429 | 430 | return MacPackager(basepath, exepath, machine, version) 431 | 432 | raise BuildError( 433 | "Cannot generate installer on your platform as it could not be determined!" 434 | ) 435 | -------------------------------------------------------------------------------- /builder/packaging/apt_install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shell script used by CI to install apt dependencies 3 | 4 | if [[ "$1" == "x86" ]]; then 5 | apt_suffix=":i386" 6 | fi 7 | 8 | deps="alien" # alien does not need ${apt_suffix} 9 | for dep in sdl2 sdl2-image sdl2-mixer sdl2-ttf sdl2-net; do 10 | deps="$deps lib${dep}-dev${apt_suffix}" 11 | done 12 | 13 | if [[ $apt_suffix == ":i386" ]]; then 14 | dpkg --add-architecture i386 15 | fi 16 | 17 | apt-get update --fix-missing 18 | apt-get upgrade -y 19 | 20 | if [[ $apt_suffix == ":i386" ]]; then 21 | apt-get install gcc-multilib g++-multilib -y 22 | fi 23 | 24 | echo Installing: $deps 25 | 26 | apt-get install $deps -y 27 | 28 | -------------------------------------------------------------------------------- /builder/packaging/debian_control.txt: -------------------------------------------------------------------------------- 1 | Package: kithare 2 | Depends: libsdl2-2.0-0, libsdl2-mixer-2.0-0, libsdl2-image-2.0-0, libsdl2-ttf-2.0-0, libsdl2-net-2.0-0 3 | Maintainer: Kithare Organization neaxture@gmail.com 4 | Homepage: https://kithare.de 5 | Description: Kithare Programming Language 6 | An open source general purpose statically-typed cross-platform 7 | interpreted/transpiled C++/Python like programming language. 8 | . 9 | The source code for Kithare programming language is distributed 10 | under the MIT license. 11 | . 12 | Copyright (C) 2022 Kithare Organization 13 | GitHub: https://github.com/avaxar/Kithare 14 | -------------------------------------------------------------------------------- /builder/packaging/debian_license.txt: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kithare 3 | Upstream-Contact: Kithare Organization 4 | Source: https://github.com/avaxar/Kithare 5 | Comment: 6 | The Expat license here is equivalent to the MIT license, that Kithare 7 | upstream ships with 8 | 9 | Files: * 10 | Copyright: 2022 Kithare Organization 11 | License: Expat 12 | -------------------------------------------------------------------------------- /builder/packaging/kithare.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Kithare 3 | Type=Application 4 | Categories=Development; 5 | Icon=kithare 6 | Comment=Programming language 7 | Terminal=true 8 | NoDisplay=false 9 | TryExec=kcr 10 | Exec=kcr %f 11 | -------------------------------------------------------------------------------- /builder/packaging/kithare_windows.iss: -------------------------------------------------------------------------------- 1 | ; Original Script generated by the Inno Setup Script Wizard, with a few modifications 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | ; functions to add/remove from PATH copied from this SO answer: 4 | ; https://stackoverflow.com/a/46609047 5 | ; Constants MyAppVersion, MyAppArch and BasePath will be defined by the build 6 | ; script while building this 7 | 8 | #define MyAppName "Kithare" 9 | #define MyAppPublisher "Kithare Organization" 10 | #define MyAppURL "https://kithare.de/" 11 | #define MyAppExeName "kcr.exe" 12 | #define MyAppAssocName MyAppName + " File" 13 | #define MyAppAssocExt ".kh" 14 | #define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt 15 | 16 | [Code] 17 | const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; 18 | 19 | procedure EnvAddPath(Path: string); 20 | var 21 | Paths: string; 22 | begin 23 | { Retrieve current path (use empty string if entry not exists) } 24 | if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) 25 | then Paths := ''; 26 | 27 | { Skip if string already found in path } 28 | if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit; 29 | 30 | { App string to the end of the path variable } 31 | Paths := Paths + ';'+ Path +';' 32 | 33 | { Overwrite (or create if missing) path environment variable } 34 | if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) 35 | then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths])) 36 | else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths])); 37 | end; 38 | 39 | procedure EnvRemovePath(Path: string); 40 | var 41 | Paths: string; 42 | P: Integer; 43 | begin 44 | { Skip if registry entry not exists } 45 | if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then 46 | exit; 47 | 48 | { Skip if string not found in path } 49 | P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';'); 50 | if P = 0 then exit; 51 | 52 | { Update path variable } 53 | Delete(Paths, P - 1, Length(Path) + 1); 54 | 55 | { Overwrite path environment variable } 56 | if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) 57 | then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths])) 58 | else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths])); 59 | end; 60 | 61 | [Setup] 62 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 63 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 64 | AppId={{50ABED83-FFA9-41E7-AF08-42B34AD861E5} 65 | AppName={#MyAppName} 66 | AppVersion={#MyAppVersion} 67 | ;AppVerName={#MyAppName} {#MyAppVersion} 68 | AppPublisher={#MyAppPublisher} 69 | AppPublisherURL={#MyAppURL} 70 | AppSupportURL={#MyAppURL} 71 | AppUpdatesURL={#MyAppURL} 72 | DefaultDirName={autopf}\{#MyAppName} 73 | ChangesAssociations=yes 74 | DisableProgramGroupPage=yes 75 | LicenseFile={#BasePath}\LICENSE.md 76 | ; Remove the following line to run in administrative install mode (install for all users.) 77 | PrivilegesRequired=lowest 78 | PrivilegesRequiredOverridesAllowed=dialog 79 | OutputBaseFilename=kithare-{#MyAppVersion}-{#MyAppArch} 80 | SetupIconFile={#BasePath}\misc\icon.ico 81 | Compression=lzma 82 | SolidCompression=yes 83 | WizardStyle=modern 84 | ChangesEnvironment=true 85 | OutputDir={#BasePath}\dist\packaging 86 | 87 | [Code] 88 | procedure CurStepChanged(CurStep: TSetupStep); 89 | begin 90 | if (CurStep = ssPostInstall) and WizardIsTaskSelected('envPath') 91 | then EnvAddPath(ExpandConstant('{app}')); 92 | end; 93 | 94 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); 95 | begin 96 | if CurUninstallStep = usPostUninstall 97 | then EnvRemovePath(ExpandConstant('{app}')); 98 | end; 99 | 100 | [Languages] 101 | Name: "english"; MessagesFile: "compiler:Default.isl" 102 | 103 | [Files] 104 | Source: "{#BasePath}\dist\MinGW-{#MyAppArch}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 105 | Source: "{#BasePath}\LICENSE.md"; DestDir: "{app}"; Flags: ignoreversion 106 | Source: "{#BasePath}\README.md"; DestDir: "{app}"; Flags: ignoreversion 107 | Source: "{#BasePath}\dist\MinGW-{#MyAppArch}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 108 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 109 | 110 | [Registry] 111 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue 112 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey 113 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0" 114 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" 115 | Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" 116 | 117 | [Icons] 118 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 119 | 120 | [Tasks] 121 | Name: envPath; Description: "Add kcr to PATH" 122 | 123 | [Run] 124 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 125 | 126 | -------------------------------------------------------------------------------- /builder/sdl_installer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/sdl_installer.py 8 | Defines classes to handle SDL installation and linker flags 9 | """ 10 | 11 | 12 | import io 13 | import platform 14 | import tarfile 15 | from pathlib import Path 16 | from typing import Union 17 | 18 | from .downloader import ThreadedDownloader 19 | from .utils import BuildError, ConvertType, convert_machine, copy, rmtree 20 | 21 | # SDL project-version pairs, remember to keep updated 22 | SDL_DEPS = { 23 | "SDL2": "2.0.22", 24 | "SDL2_image": "2.0.5", 25 | "SDL2_mixer": "2.0.4", 26 | "SDL2_ttf": "2.0.18", 27 | "SDL2_net": "2.0.1", 28 | } 29 | 30 | 31 | class SDLInstaller: 32 | """ 33 | SDLInstaller base class, with limited functionality. This class is kept 34 | for compatability with non-windows and non-mac OSes 35 | """ 36 | 37 | def __init__(self): 38 | """ 39 | Initialise SDLInstaller class 40 | """ 41 | self.ldflags: list[Union[str, Path]] = [] 42 | 43 | def install_all(self): 44 | """ 45 | Utility function to install all SDL deps. In this dummy installer, only 46 | updates ldflags to link with already installed SDL 47 | """ 48 | for name in SDL_DEPS: 49 | self.ldflags.append(f"-l{name}") 50 | if name == "SDL2": 51 | self.ldflags.append("-lSDL2main") 52 | 53 | 54 | class WindowsSDLInstaller(SDLInstaller): 55 | """ 56 | Helper class to install SDL deps on MinGW 57 | """ 58 | 59 | def __init__(self, basepath: Path, dist_dir: Path, machine: str): 60 | """ 61 | Initialise WindowsSDLInstaller class 62 | """ 63 | super().__init__() 64 | self.sdl_dir = basepath / "deps" / "SDL" 65 | self.sdl_include = self.sdl_dir / "include" / "SDL2" 66 | 67 | self.dist_dir = dist_dir 68 | self.downloader = ThreadedDownloader() 69 | 70 | self.machine = convert_machine(machine, ConvertType.WINDOWS_MINGW) 71 | 72 | def _prepare_install(self): 73 | """ 74 | Prepare for install, delete any outdated installs, prepare download 75 | path. 76 | """ 77 | if self.sdl_dir.is_dir(): 78 | # delete old SDL version installations, if any 79 | saved_dirs = {f"{n}-{v}" for n, v in SDL_DEPS.items()} 80 | saved_dirs.add("include") 81 | for subdir in self.sdl_dir.iterdir(): 82 | if subdir.name not in saved_dirs: 83 | print(f"Deleting old SDL install: '{subdir.name}'") 84 | rmtree(subdir) 85 | 86 | # make SDL include dir 87 | self.sdl_include.mkdir(parents=True, exist_ok=True) 88 | 89 | def _download_dep(self, name: str, ver: str): 90 | """ 91 | Download an SDL dep, uses ThreadedDownloader to download in background. 92 | Return a two element tuple, first one indicating whether download was 93 | skipped, second Path object to downloaded dir 94 | """ 95 | sdl_mingw_dep = self.sdl_dir / f"{name}-{ver}" / self.machine 96 | if sdl_mingw_dep.is_dir(): 97 | print(f"Skipping {name} download because it already exists") 98 | return True, sdl_mingw_dep 99 | 100 | download_link = "https://www.libsdl.org/" 101 | if name != "SDL2": 102 | download_link += f"projects/{name}/".replace("2", "") 103 | 104 | download_link += f"release/{name}-devel-{ver}-mingw.tar.gz" 105 | 106 | self.downloader.download(name, download_link) 107 | return False, sdl_mingw_dep 108 | 109 | def _extract(self, name: str, downloaddata: bytes, downloaded_path: Path): 110 | """ 111 | Extract downloaded dep into SDL deps folder 112 | """ 113 | try: 114 | with io.BytesIO(downloaddata) as fileobj: 115 | with tarfile.open(mode="r:gz", fileobj=fileobj) as tarred: 116 | tarred.extractall(self.sdl_dir) 117 | 118 | except (tarfile.TarError, OSError): 119 | # some error while extracting 120 | rmtree(downloaded_path.parent) # clean failed download 121 | raise BuildError( 122 | f"Failed to extract tarfile of {name} while downloading" 123 | ) from None 124 | 125 | # Copy includes 126 | for header in downloaded_path.glob("include/SDL2/*.h"): 127 | copy(header, self.sdl_include) 128 | 129 | def _copy_dll(self, path: Path, overwrite: bool = True): 130 | """ 131 | Copy DLLs from downloaded path and update ldflags with libpath 132 | """ 133 | # Copy DLLs that have not been copied already 134 | for dll in path.glob("bin/*.dll"): 135 | copy(dll, self.dist_dir, overwrite) 136 | 137 | self.ldflags.append(path / "lib") 138 | 139 | def install_all(self): 140 | """ 141 | Utility function to install all SDL deps. Deletes any old SDL install, 142 | and downloads the deps concurrently, and returns a two element tuple, 143 | first being flag for SDL include, and second is a list of SDL linking 144 | linker flags. 145 | """ 146 | print("Configuring SDL dependencies...") 147 | print("Any missing dependencies will be downloaded") 148 | print("This might take a while, depending on your internet speeds") 149 | 150 | self._prepare_install() 151 | super().install_all() 152 | 153 | downloads: dict[str, Path] = {} 154 | 155 | # Download SDL deps if unavailable, use threading to download deps 156 | # concurrently 157 | for name, ver in SDL_DEPS.items(): 158 | skipped, path = self._download_dep(name, ver) 159 | if skipped: 160 | # download was skipped, update lflags and copy any DLLs 161 | self._copy_dll(path, False) 162 | else: 163 | downloads[name] = path 164 | 165 | # extract dependencies 166 | for name, downloaddata in self.downloader.get_finished(): 167 | self._extract(name, downloaddata, downloads[name]) 168 | 169 | # copy DLLs from extracted dependencies 170 | for path in downloads.values(): 171 | self._copy_dll(path) 172 | 173 | print() # newline 174 | return self.sdl_include.parent 175 | 176 | 177 | class MacSDLInstaller(SDLInstaller): 178 | """ 179 | Helper class to install SDL deps on MacOS. 180 | TODO: Fully implement this class with better SDL and dep handling on Mac 181 | """ 182 | 183 | 184 | def get_installer(basepath: Path, dist_dir: Path, machine: str): 185 | """ 186 | Gets an instance of the platform specific installer class, fallback to base 187 | class on other OSes 188 | """ 189 | if platform.system() == "Windows": 190 | return WindowsSDLInstaller(basepath, dist_dir, machine) 191 | 192 | if platform.system() == "Darwin": 193 | return MacSDLInstaller() 194 | 195 | return SDLInstaller() 196 | -------------------------------------------------------------------------------- /builder/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT 4 | license. 5 | Copyright (C) 2022 Kithare Organization 6 | 7 | builder/utils.py 8 | Defines common builder utility functions 9 | """ 10 | 11 | import argparse 12 | import platform 13 | import shutil 14 | import stat 15 | import subprocess 16 | 17 | from datetime import datetime 18 | from enum import Enum, auto 19 | from functools import lru_cache 20 | from pathlib import Path 21 | from typing import Union 22 | 23 | from .constants import EPILOG, INIT_TEXT 24 | 25 | # While we recursively search for include files, we don't want to seach 26 | # the whole file, because that would waste a lotta time. So, we just take 27 | # an arbitrary line number limit, beyond which, we won't search 28 | INC_FILE_LINE_LIMIT = 75 29 | 30 | TIMESTAMP = ( 31 | datetime.utcnow().isoformat(".", "minutes").replace("-", ".").replace(":", "") 32 | ) 33 | NIGHLY_BUILD_VER = f"{TIMESTAMP}-nightly" 34 | 35 | 36 | class ConvertType(Enum): 37 | """ 38 | Enum for input for convert_machine 39 | """ 40 | 41 | WINDOWS = auto() 42 | WINDOWS_MINGW = auto() 43 | MAC = auto() 44 | LINUX_DEB = auto() 45 | LINUX_ARCH = auto() 46 | LINUX_RPM = auto() 47 | APP_IMAGE = auto() 48 | 49 | 50 | class BuildError(Exception): 51 | """ 52 | Exception class for all build related exceptions 53 | """ 54 | 55 | def __init__(self, emsg: str = "", ecode: int = 1): 56 | """ 57 | Initialise BuildError exception object 58 | """ 59 | super().__init__(self, emsg) 60 | self.emsg = emsg 61 | self.ecode = ecode 62 | 63 | 64 | def get_rel_path(dirpath: Path, basepath: Path = Path().resolve()): 65 | """ 66 | Get dirpath as relative to basepath. This handles corner cases better than 67 | Path.relative_to, uses ".." path notation so that the relative path is 68 | always obtained no matter where the two paths are located. Here, dirpath 69 | and basepath must be fully resolved absolute paths to directories. By 70 | default, basepath is current working dir 71 | """ 72 | ret_parts: list[str] = [] 73 | back_parts = 0 74 | has_seperated = False 75 | 76 | cnt = -1 77 | for cnt, part in enumerate(dirpath.parts): 78 | if cnt >= len(basepath.parts): 79 | ret_parts.append(part) 80 | continue 81 | 82 | # got a mismatching parent dir 83 | if basepath.parts[cnt] != part: 84 | has_seperated = True 85 | 86 | if has_seperated: 87 | ret_parts.append(part) 88 | back_parts += 1 89 | 90 | cnt += 1 91 | back_parts += max(len(basepath.parts) - cnt, 0) 92 | 93 | ret = Path(*([".."] * back_parts)) 94 | for part in ret_parts: 95 | ret /= part 96 | 97 | return ret 98 | 99 | 100 | def run_cmd(*cmds: Union[str, Path], strict: bool = False, silent_cmds: bool = False): 101 | """ 102 | Helper function to run command in subprocess. 103 | Prints the command, command output, and error exit code (if nonzero), and 104 | also returns the exit code. If strict arg is given True, BuildError is 105 | raised rather than printing the error. 106 | """ 107 | if not silent_cmds: 108 | print(">", *cmds) 109 | 110 | # run with subprocess 111 | try: 112 | proc = subprocess.run( 113 | list(map(str, cmds)), 114 | stdout=subprocess.PIPE, 115 | stderr=subprocess.STDOUT, 116 | universal_newlines=True, 117 | check=False, 118 | ) 119 | 120 | except FileNotFoundError: 121 | emsg = f"The command '{cmds[0]}' was not found" 122 | if strict: 123 | raise BuildError(emsg) from None 124 | 125 | print("BuildError:", emsg) 126 | return 1 127 | 128 | print(proc.stdout, end="") 129 | if proc.returncode: 130 | emsg = f"'{cmds[0]}' command failed with exit code {proc.returncode}" 131 | if abs(proc.returncode) > 9: 132 | emsg += f" ({hex(proc.returncode)})" 133 | 134 | if strict: 135 | raise BuildError(emsg, proc.returncode) 136 | 137 | print("BuildError:", emsg) 138 | 139 | return proc.returncode 140 | 141 | 142 | @lru_cache(maxsize=256) 143 | def find_includes_max_time(file: Path, incdir: Path) -> float: 144 | """ 145 | Recursively find include files for a given file. Returns the latest time a 146 | file was modified 147 | """ 148 | if not file.suffix: 149 | # no suffix for filename, C++ stdlib header 150 | return -1 151 | 152 | try: 153 | ret = file.stat().st_mtime 154 | except FileNotFoundError: 155 | return -1 156 | 157 | for line in file.read_text().splitlines()[:INC_FILE_LINE_LIMIT]: 158 | words = line.split() 159 | if len(words) < 2 or words[0] != "#include": 160 | # not an include line 161 | continue 162 | 163 | if words[1].startswith("<") and words[1].endswith(">"): 164 | fname = words[1][1:-1] 165 | ret = max(ret, find_includes_max_time(incdir / fname, incdir)) 166 | 167 | fname = words[1].strip('"') 168 | if fname != words[1]: 169 | ret = max(ret, find_includes_max_time(file.parent / fname, incdir)) 170 | ret = max(ret, find_includes_max_time(incdir / fname, incdir)) 171 | 172 | return ret 173 | 174 | 175 | def should_build(file: Path, ofile: Path, incdir: Path): 176 | """ 177 | Determines whether a particular source file should be rebuilt 178 | """ 179 | try: 180 | # a file, or an included file was newer than the object file 181 | return find_includes_max_time(file, incdir) > ofile.stat().st_mtime 182 | except FileNotFoundError: 183 | return True 184 | 185 | 186 | def rmtree(top: Path): 187 | """ 188 | Reimplementation of shutil.rmtree, used to remove a directory. Returns a 189 | boolean on whether top was a directory or not (in the latter case this 190 | function does nothing). This function may raise BuildErrors on any internal 191 | OSErrors that happen. 192 | The reason shutil.rmtree itself is not used, is of a permission error on 193 | Windows. 194 | """ 195 | if not top.is_dir(): 196 | return False 197 | 198 | try: 199 | for newpath in top.iterdir(): 200 | if not rmtree(newpath): 201 | # could not rmtree newpath because it is a file, hence unlink 202 | newpath.chmod(stat.S_IWUSR) 203 | newpath.unlink() 204 | 205 | top.rmdir() 206 | return True 207 | except OSError: 208 | raise BuildError(f"Could not delete directory '{top}'") from None 209 | 210 | 211 | def copy(file: Path, dest: Path, overwrite: bool = True): 212 | """ 213 | Thin wrapper around shutil.copy, raises BuildError if the copy failed. 214 | Also, overwrite arg is a bool that indicates whether the file is 215 | overwritten if it already exists. 216 | Returns Path object to the created file. 217 | """ 218 | if not overwrite and (dest / file.name).exists(): 219 | return dest / file.name 220 | 221 | try: 222 | return Path(shutil.copy(file, dest)) 223 | except OSError: 224 | raise BuildError( 225 | f"Could not copy file '{file}' to '{dest}' directory" 226 | ) from None 227 | 228 | 229 | def get_machine(is_32_bit: bool): 230 | """ 231 | Utility to get string representation of the machine name. Possible return 232 | values: 233 | name | Description | Aliases 234 | ---------------------------------------- 235 | x86 | Intel/AMD 32-bit | i386, i686 236 | x64 | Intel/AMD 64-bit | x86_64, amd64 237 | arm | ARM 32-bit (very old) | armel, armv5 (or older) 238 | armv6 | ARM 32-bit (old) | armhf, armv6l, armv6h 239 | armv7 | ARM 32-bit | armhf, armv7l, armv7h 240 | arm64 | ARM 64-bit | aarch64, armv8l, armv8 (or newer) 241 | ppc64le | PowerPC achitecture | ppc64el (debian terminology) 242 | s390x | IBM (big endian) | None 243 | Unknown | Architecture could not be determined 244 | 245 | The function can also return other values platform.machine returns, without 246 | any modifications 247 | """ 248 | machine = platform.machine() 249 | machine_lowered = machine.lower() 250 | if machine.endswith("86"): 251 | return "x86" 252 | 253 | if machine_lowered in {"x86_64", "amd64", "x64"}: 254 | return "x86" if is_32_bit else "x64" 255 | 256 | if machine_lowered in {"armv8", "armv8l", "arm64", "aarch64"}: 257 | return "armv7" if is_32_bit else "arm64" 258 | 259 | if machine_lowered.startswith("arm"): 260 | if "v7" in machine_lowered or "hf" in machine_lowered: 261 | return "armv7" 262 | 263 | if "v6" in machine_lowered: 264 | return "armv6" 265 | 266 | return "arm" 267 | 268 | if machine == "ppc64el": 269 | return "ppc64le" 270 | 271 | if not machine: 272 | return "Unknown" 273 | 274 | return machine 275 | 276 | 277 | def convert_machine(machine: str, mode: ConvertType): 278 | """ 279 | Convert machine returned by get_machine to another format 280 | 281 | Here is a table of what this function does 282 | 283 | name | Windows | Mac | Debian | Arch | RPM | AppImage | MinGW 284 | --------------------------------------------------------------------- -------------------- 285 | x86 | x86 | i686 | i386 | i686 | i686 | i686 | i686-w64-mingw32 286 | x64 | x64 | x86_64 | amd64 | x86_64 | x86_64 | x86_64 | x86_64-w64-mingw32 287 | arm | Errors | Errors | armel | arm | armv5tel | Errors | Errors 288 | armv6 | Errors | Errors | armhf | armv6h | armv6l | Errors | Errors 289 | armv7 | Errors | Errors | armhf | armv7h | armv7l | armhf | Errors 290 | arm64 | Errors | arm64 | arm64 | aarch64 | aarch64 | aarch64 | Errors 291 | ppc64le | Errors | Errors | ppc64el | Errors | ppc64le | Errors | Errors 292 | Others | Errors | Errors | Returns | Errors | Returns | Errors | Errors 293 | Unknown | Errors | Errors | Errors | Errors | Errors | Errors | Errors 294 | """ 295 | 296 | if mode not in ConvertType: 297 | raise ValueError("Bug in builder, received invalid mode arg") 298 | 299 | if machine.lower() == "unknown": 300 | raise BuildError( 301 | "Your CPU Architecture could not be determined, an installer " 302 | "cannot be made" 303 | ) 304 | 305 | if machine == "x86": 306 | if mode == ConvertType.LINUX_DEB: 307 | return "i386" 308 | 309 | if mode == ConvertType.WINDOWS: 310 | return "x86" 311 | 312 | ret = "i686" 313 | if mode == ConvertType.WINDOWS_MINGW: 314 | ret += "-w64-mingw32" 315 | 316 | return ret 317 | 318 | if machine == "x64": 319 | if mode == ConvertType.LINUX_DEB: 320 | return "amd64" 321 | 322 | if mode == ConvertType.WINDOWS: 323 | return "x64" 324 | 325 | ret = "x86_64" 326 | if mode == ConvertType.WINDOWS_MINGW: 327 | ret += "-w64-mingw32" 328 | 329 | return ret 330 | 331 | if machine == "arm": 332 | if mode == ConvertType.LINUX_DEB: 333 | return "armel" 334 | 335 | if mode == ConvertType.LINUX_ARCH: 336 | return "arm" 337 | 338 | if mode == ConvertType.LINUX_RPM: 339 | return "armv5tel" 340 | 341 | if machine in {"armv6", "armv7"}: 342 | if mode == ConvertType.LINUX_DEB or ( 343 | mode == ConvertType.APP_IMAGE and machine == "armv7" 344 | ): 345 | return "armhf" 346 | 347 | if mode == ConvertType.LINUX_ARCH: 348 | return machine + "h" 349 | 350 | if mode == ConvertType.LINUX_RPM: 351 | return machine + "l" 352 | 353 | if machine == "arm64": 354 | if mode in {ConvertType.MAC, ConvertType.LINUX_DEB}: 355 | return "arm64" 356 | 357 | if mode in { 358 | ConvertType.LINUX_ARCH, 359 | ConvertType.LINUX_RPM, 360 | ConvertType.APP_IMAGE, 361 | }: 362 | return "aarch64" 363 | 364 | if machine == "ppc64le": 365 | if mode == ConvertType.LINUX_DEB: 366 | return "ppc64el" 367 | 368 | if mode == ConvertType.LINUX_RPM: 369 | return "ppc64le" 370 | 371 | if mode in {ConvertType.LINUX_DEB, ConvertType.LINUX_RPM}: 372 | return machine 373 | 374 | if mode == ConvertType.APP_IMAGE: 375 | raise BuildError( 376 | f"installers for {machine} CPU are not supported with AppImage" 377 | ) 378 | 379 | raise BuildError(f"Installers for {machine} CPU are not supported on this platform") 380 | 381 | 382 | def parse_args(): 383 | """ 384 | Parse commandline args using the argparse module 385 | """ 386 | parser = argparse.ArgumentParser( 387 | description=INIT_TEXT, 388 | epilog=EPILOG, 389 | formatter_class=argparse.RawDescriptionHelpFormatter, 390 | ) 391 | 392 | parser.add_argument( 393 | "--make", 394 | choices=("debug", "test", "installer"), 395 | help="Specifies the action that the build script should take", 396 | ) 397 | 398 | parser.add_argument( 399 | "--clean", 400 | metavar="level", 401 | type=str, 402 | help=( 403 | "Clean action: can be dep, build, dist, package or any combination of those" 404 | ), 405 | ) 406 | 407 | parser.add_argument( 408 | "-j", 409 | metavar="cores", 410 | type=int, 411 | help="Specifies the number of cores to use during compilation", 412 | ) 413 | 414 | parser.add_argument( 415 | "--arch", 416 | metavar="architecture", 417 | choices=("x64", "x86"), 418 | default="x64", 419 | help="A flag that can be x64 for 64-bit, and x86 for 32-bit", 420 | ) 421 | 422 | parser.add_argument( 423 | "--release", 424 | metavar="version", 425 | default=NIGHLY_BUILD_VER, 426 | help="Used to set the version on the installer (PROVISIONAL FLAG, DO NOT USE)", 427 | ) 428 | 429 | parser.add_argument( 430 | "--use-alien", 431 | action="store_true", 432 | help="A flag that indicates whether to use 'alien' command on installers", 433 | ) 434 | 435 | ret = parser.parse_args() 436 | return ret.arch.endswith("86"), ret 437 | -------------------------------------------------------------------------------- /deps/README.txt: -------------------------------------------------------------------------------- 1 | This directory is where dependencies are stored. 2 | -------------------------------------------------------------------------------- /include/kithare/core/ast.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | typedef struct khAstStatement khAstStatement; 22 | typedef struct khAstExpression khAstExpression; 23 | 24 | 25 | typedef enum { 26 | khAstStatementType_INVALID, 27 | 28 | khAstStatementType_VARIABLE, 29 | khAstStatementType_EXPRESSION, 30 | 31 | khAstStatementType_IMPORT, 32 | khAstStatementType_INCLUDE, 33 | khAstStatementType_FUNCTION, 34 | khAstStatementType_CLASS, 35 | khAstStatementType_STRUCT, 36 | khAstStatementType_ENUM, 37 | khAstStatementType_ALIAS, 38 | 39 | khAstStatementType_IF_BRANCH, 40 | khAstStatementType_WHILE_LOOP, 41 | khAstStatementType_DO_WHILE_LOOP, 42 | khAstStatementType_FOR_LOOP, 43 | khAstStatementType_BREAK, 44 | khAstStatementType_CONTINUE, 45 | khAstStatementType_RETURN 46 | } khAstStatementType; 47 | 48 | khstring khAstStatementType_string(khAstStatementType type); 49 | 50 | 51 | // This variable declaration AST struct is declared up here, as it is used by khAstLambda. 52 | typedef struct { 53 | bool is_static; 54 | bool is_wild; 55 | bool is_ref; 56 | kharray(khstring) names; 57 | khAstExpression* opt_type; 58 | khAstExpression* opt_initializer; 59 | } khAstVariable; 60 | 61 | khAstVariable khAstVariable_copy(khAstVariable* variable); 62 | void khAstVariable_delete(khAstVariable* variable); 63 | khstring khAstVariable_string(khAstVariable* variable, char32_t* origin); 64 | 65 | 66 | typedef enum { 67 | khAstExpressionType_INVALID, 68 | 69 | khAstExpressionType_IDENTIFIER, 70 | khAstExpressionType_CHAR, 71 | khAstExpressionType_STRING, 72 | khAstExpressionType_BUFFER, 73 | khAstExpressionType_BYTE, 74 | khAstExpressionType_INTEGER, 75 | khAstExpressionType_UINTEGER, 76 | khAstExpressionType_FLOAT, 77 | khAstExpressionType_DOUBLE, 78 | khAstExpressionType_IFLOAT, 79 | khAstExpressionType_IDOUBLE, 80 | 81 | khAstExpressionType_TUPLE, 82 | khAstExpressionType_ARRAY, 83 | khAstExpressionType_DICT, 84 | khAstExpressionType_ELLIPSIS, 85 | 86 | khAstExpressionType_SIGNATURE, 87 | khAstExpressionType_LAMBDA, 88 | 89 | khAstExpressionType_UNARY, 90 | khAstExpressionType_BINARY, 91 | khAstExpressionType_TERNARY, 92 | khAstExpressionType_COMPARISON, 93 | khAstExpressionType_CALL, 94 | khAstExpressionType_INDEX, 95 | 96 | khAstExpressionType_SCOPE, 97 | khAstExpressionType_TEMPLATIZE 98 | } khAstExpressionType; 99 | 100 | khstring khAstExpressionType_string(khAstExpressionType type); 101 | 102 | 103 | typedef struct { 104 | kharray(khAstExpression) values; 105 | } khAstTuple; 106 | 107 | khAstTuple khAstTuple_copy(khAstTuple* tuple); 108 | void khAstTuple_delete(khAstTuple* tuple); 109 | khstring khAstTuple_string(khAstTuple* tuple, char32_t* origin); 110 | 111 | 112 | typedef struct { 113 | kharray(khAstExpression) values; 114 | } khAstArray; 115 | 116 | khAstArray khAstArray_copy(khAstArray* array); 117 | void khAstArray_delete(khAstArray* array); 118 | khstring khAstArray_string(khAstArray* array, char32_t* origin); 119 | 120 | 121 | typedef struct { 122 | kharray(khAstExpression) keys; 123 | kharray(khAstExpression) values; 124 | } khAstDict; 125 | 126 | khAstDict khAstDict_copy(khAstDict* dict); 127 | void khAstDict_delete(khAstDict* dict); 128 | khstring khAstDict_string(khAstDict* dict, char32_t* origin); 129 | 130 | 131 | typedef struct { 132 | kharray(bool) are_arguments_refs; 133 | kharray(khAstExpression) argument_types; 134 | bool is_return_type_ref; 135 | khAstExpression* opt_return_type; 136 | } khAstSignature; 137 | 138 | khAstSignature khAstSignature_copy(khAstSignature* signature); 139 | 140 | void khAstSignature_delete(khAstSignature* signature); 141 | khstring khAstSignature_string(khAstSignature* signature, char32_t* origin); 142 | 143 | 144 | typedef struct { 145 | kharray(khAstVariable) arguments; 146 | khAstVariable* opt_variadic_argument; 147 | bool is_return_type_ref; 148 | khAstExpression* opt_return_type; 149 | kharray(khAstStatement) block; 150 | } khAstLambda; 151 | 152 | khAstLambda khAstLambda_copy(khAstLambda* lambda); 153 | void khAstLambda_delete(khAstLambda* lambda); 154 | khstring khAstLambda_string(khAstLambda* lambda, char32_t* origin); 155 | 156 | 157 | typedef enum { 158 | khAstUnaryExpressionType_POSITIVE, 159 | khAstUnaryExpressionType_NEGATIVE, 160 | 161 | khAstUnaryExpressionType_NOT, 162 | khAstUnaryExpressionType_BIT_NOT 163 | } khAstUnaryExpressionType; 164 | 165 | khstring khAstUnaryExpressionType_string(khAstUnaryExpressionType type); 166 | 167 | 168 | typedef struct { 169 | khAstUnaryExpressionType type; 170 | khAstExpression* operand; 171 | } khAstUnaryExpression; 172 | 173 | khAstUnaryExpression khAstUnaryExpression_copy(khAstUnaryExpression* unary_exp); 174 | void khAstUnaryExpression_delete(khAstUnaryExpression* unary_exp); 175 | khstring khAstUnaryExpression_string(khAstUnaryExpression* unary_exp, char32_t* origin); 176 | 177 | 178 | typedef enum { 179 | khAstBinaryExpressionType_ASSIGN, 180 | khAstBinaryExpressionType_RANGE, 181 | 182 | khAstBinaryExpressionType_ADD, 183 | khAstBinaryExpressionType_SUB, 184 | khAstBinaryExpressionType_MUL, 185 | khAstBinaryExpressionType_DIV, 186 | khAstBinaryExpressionType_MOD, 187 | khAstBinaryExpressionType_DOT, 188 | khAstBinaryExpressionType_POW, 189 | 190 | khAstBinaryExpressionType_IP_ADD, 191 | khAstBinaryExpressionType_IP_SUB, 192 | khAstBinaryExpressionType_IP_MUL, 193 | khAstBinaryExpressionType_IP_DIV, 194 | khAstBinaryExpressionType_IP_MOD, 195 | khAstBinaryExpressionType_IP_DOT, 196 | khAstBinaryExpressionType_IP_POW, 197 | 198 | khAstBinaryExpressionType_AND, 199 | khAstBinaryExpressionType_OR, 200 | khAstBinaryExpressionType_XOR, 201 | 202 | khAstBinaryExpressionType_BIT_AND, 203 | khAstBinaryExpressionType_BIT_OR, 204 | khAstBinaryExpressionType_BIT_XOR, 205 | khAstBinaryExpressionType_BIT_LSHIFT, 206 | khAstBinaryExpressionType_BIT_RSHIFT, 207 | 208 | khAstBinaryExpressionType_IP_BIT_AND, 209 | khAstBinaryExpressionType_IP_BIT_OR, 210 | khAstBinaryExpressionType_IP_BIT_XOR, 211 | khAstBinaryExpressionType_IP_BIT_LSHIFT, 212 | khAstBinaryExpressionType_IP_BIT_RSHIFT 213 | } khAstBinaryExpressionType; 214 | 215 | khstring khAstBinaryExpressionType_string(khAstBinaryExpressionType type); 216 | 217 | 218 | typedef struct { 219 | khAstBinaryExpressionType type; 220 | khAstExpression* left; 221 | khAstExpression* right; 222 | } khAstBinaryExpression; 223 | 224 | khAstBinaryExpression khAstBinaryExpression_copy(khAstBinaryExpression* binary_exp); 225 | void khAstBinaryExpression_delete(khAstBinaryExpression* binary_exp); 226 | khstring khAstBinaryExpression_string(khAstBinaryExpression* binary_exp, char32_t* origin); 227 | 228 | 229 | typedef struct { 230 | khAstExpression* condition; 231 | khAstExpression* value; 232 | khAstExpression* otherwise; 233 | } khAstTernaryExpression; 234 | 235 | khAstTernaryExpression khAstTernaryExpression_copy(khAstTernaryExpression* ternary_exp); 236 | void khAstTernaryExpression_delete(khAstTernaryExpression* ternary_exp); 237 | khstring khAstTernaryExpression_string(khAstTernaryExpression* ternary_exp, char32_t* origin); 238 | 239 | 240 | typedef enum { 241 | khAstComparisonExpressionType_EQUAL, 242 | khAstComparisonExpressionType_UNEQUAL, 243 | khAstComparisonExpressionType_LESS, 244 | khAstComparisonExpressionType_GREATER, 245 | khAstComparisonExpressionType_LESS_EQUAL, 246 | khAstComparisonExpressionType_GREATER_EQUAL 247 | } khAstComparisonExpressionType; 248 | 249 | khstring khAstComparisonExpressionType_string(khAstComparisonExpressionType type); 250 | 251 | 252 | typedef struct { 253 | kharray(khAstComparisonExpressionType) operations; 254 | kharray(khAstExpression) operands; 255 | } khAstComparisonExpression; 256 | 257 | khAstComparisonExpression khAstComparisonExpression_copy(khAstComparisonExpression* comparison_exp); 258 | void khAstComparisonExpression_delete(khAstComparisonExpression* comparison_exp); 259 | khstring khAstComparisonExpression_string(khAstComparisonExpression* comparison_exp, char32_t* origin); 260 | 261 | 262 | typedef struct { 263 | khAstExpression* callee; 264 | kharray(khAstExpression) arguments; 265 | } khAstCallExpression; 266 | 267 | khAstCallExpression khAstCallExpression_copy(khAstCallExpression* call_exp); 268 | void khAstCallExpression_delete(khAstCallExpression* call_exp); 269 | khstring khAstCallExpression_string(khAstCallExpression* call_exp, char32_t* origin); 270 | 271 | 272 | typedef struct { 273 | khAstExpression* indexee; 274 | kharray(khAstExpression) arguments; 275 | } khAstIndexExpression; 276 | 277 | khAstIndexExpression khAstIndexExpression_copy(khAstIndexExpression* index_exp); 278 | void khAstIndexExpression_delete(khAstIndexExpression* index_exp); 279 | khstring khAstIndexExpression_string(khAstIndexExpression* index_exp, char32_t* origin); 280 | 281 | 282 | typedef struct { 283 | khAstExpression* value; 284 | kharray(khstring) scope_names; 285 | } khAstScopeExpression; 286 | 287 | khAstScopeExpression khAstScopeExpression_copy(khAstScopeExpression* scope_exp); 288 | void khAstScopeExpression_delete(khAstScopeExpression* scope_exp); 289 | khstring khAstScopeExpression_string(khAstScopeExpression* scope_exp, char32_t* origin); 290 | 291 | 292 | typedef struct { 293 | khAstExpression* value; 294 | kharray(khAstExpression) template_arguments; 295 | } khAstTemplatizeExpression; 296 | 297 | khAstTemplatizeExpression khAstTemplatizeExpression_copy(khAstTemplatizeExpression* templatize_exp); 298 | void khAstTemplatizeExpression_delete(khAstTemplatizeExpression* templatize_exp); 299 | khstring khAstTemplatizeExpression_string(khAstTemplatizeExpression* templatize_exp, char32_t* origin); 300 | 301 | 302 | struct khAstExpression { 303 | char32_t* begin; 304 | char32_t* end; 305 | 306 | khAstExpressionType type; 307 | union { 308 | khstring identifier; 309 | char32_t char_v; 310 | khstring string; 311 | khbuffer buffer; 312 | uint8_t byte; 313 | int64_t integer; 314 | uint64_t uinteger; 315 | float float_v; 316 | double double_v; 317 | float ifloat; 318 | double idouble; 319 | 320 | khAstTuple tuple; 321 | khAstArray array; 322 | khAstDict dict; 323 | 324 | khAstSignature signature; 325 | khAstLambda lambda; 326 | 327 | khAstUnaryExpression unary; 328 | khAstBinaryExpression binary; 329 | khAstTernaryExpression ternary; 330 | khAstComparisonExpression comparison; 331 | khAstCallExpression call; 332 | khAstIndexExpression index; 333 | 334 | khAstScopeExpression scope; 335 | khAstTemplatizeExpression templatize; 336 | }; 337 | }; 338 | 339 | khAstExpression khAstExpression_copy(khAstExpression* expression); 340 | void khAstExpression_delete(khAstExpression* expression); 341 | khstring khAstExpression_string(khAstExpression* expression, char32_t* origin); 342 | 343 | 344 | typedef struct { 345 | kharray(khstring) path; 346 | bool relative; 347 | khstring* opt_alias; 348 | } khAstImport; 349 | 350 | khAstImport khAstImport_copy(khAstImport* import_v); 351 | void khAstImport_delete(khAstImport* import_v); 352 | khstring khAstImport_string(khAstImport* import_v, char32_t* origin); 353 | 354 | 355 | typedef struct { 356 | kharray(khstring) path; 357 | bool relative; 358 | } khAstInclude; 359 | 360 | khAstInclude khAstInclude_copy(khAstInclude* include); 361 | void khAstInclude_delete(khAstInclude* include); 362 | khstring khAstInclude_string(khAstInclude* include, char32_t* origin); 363 | 364 | 365 | typedef struct { 366 | bool is_incase; 367 | bool is_static; 368 | kharray(khstring) identifiers; 369 | kharray(khstring) template_arguments; 370 | kharray(khAstVariable) arguments; 371 | khAstVariable* opt_variadic_argument; 372 | bool is_return_type_ref; 373 | khAstExpression* opt_return_type; 374 | kharray(khAstStatement) block; 375 | } khAstFunction; 376 | 377 | khAstFunction khAstFunction_copy(khAstFunction* function); 378 | void khAstFunction_delete(khAstFunction* function); 379 | khstring khAstFunction_string(khAstFunction* function, char32_t* origin); 380 | 381 | 382 | typedef struct { 383 | bool is_incase; 384 | khstring name; 385 | kharray(khstring) template_arguments; 386 | khAstExpression* opt_base_type; 387 | kharray(khAstStatement) block; 388 | } khAstClass; 389 | 390 | khAstClass khAstClass_copy(khAstClass* class_v); 391 | void khAstClass_delete(khAstClass* class_v); 392 | khstring khAstClass_string(khAstClass* class_v, char32_t* origin); 393 | 394 | 395 | typedef struct { 396 | bool is_incase; 397 | khstring name; 398 | kharray(khstring) template_arguments; 399 | kharray(khAstStatement) block; 400 | } khAstStruct; 401 | 402 | khAstStruct khAstStruct_copy(khAstStruct* struct_v); 403 | void khAstStruct_delete(khAstStruct* struct_v); 404 | khstring khAstStruct_string(khAstStruct* struct_v, char32_t* origin); 405 | 406 | 407 | typedef struct { 408 | khstring name; 409 | kharray(khstring) members; 410 | } khAstEnum; 411 | 412 | khAstEnum khAstEnum_copy(khAstEnum* enum_v); 413 | void khAstEnum_delete(khAstEnum* enum_v); 414 | khstring khAstEnum_string(khAstEnum* enum_v, char32_t* origin); 415 | 416 | 417 | typedef struct { 418 | bool is_incase; 419 | khstring name; 420 | khAstExpression expression; 421 | } khAstAlias; 422 | 423 | khAstAlias khAstAlias_copy(khAstAlias* alias); 424 | void khAstAlias_delete(khAstAlias* alias); 425 | khstring khAstAlias_string(khAstAlias* alias, char32_t* origin); 426 | 427 | 428 | typedef struct { 429 | kharray(khAstExpression) branch_conditions; 430 | kharray(kharray(khAstStatement)) branch_blocks; 431 | kharray(khAstStatement) else_block; 432 | } khAstIfBranch; 433 | 434 | khAstIfBranch khAstIfBranch_copy(khAstIfBranch* if_branch); 435 | void khAstIfBranch_delete(khAstIfBranch* if_branch); 436 | khstring khAstIfBranch_string(khAstIfBranch* if_branch, char32_t* origin); 437 | 438 | 439 | typedef struct { 440 | khAstExpression condition; 441 | kharray(khAstStatement) block; 442 | } khAstWhileLoop; 443 | 444 | khAstWhileLoop khAstWhileLoop_copy(khAstWhileLoop* while_loop); 445 | void khAstWhileLoop_delete(khAstWhileLoop* while_loop); 446 | khstring khAstWhileLoop_string(khAstWhileLoop* while_loop, char32_t* origin); 447 | 448 | 449 | typedef struct { 450 | khAstExpression condition; 451 | kharray(khAstStatement) block; 452 | } khAstDoWhileLoop; 453 | 454 | khAstDoWhileLoop khAstDoWhileLoop_copy(khAstDoWhileLoop* do_while_loop); 455 | void khAstDoWhileLoop_delete(khAstDoWhileLoop* do_while_loop); 456 | khstring khAstDoWhileLoop_string(khAstDoWhileLoop* do_while_loop, char32_t* origin); 457 | 458 | 459 | typedef struct { 460 | kharray(khstring) iterators; 461 | khAstExpression iteratee; 462 | kharray(khAstStatement) block; 463 | } khAstForLoop; 464 | 465 | khAstForLoop khAstForLoop_copy(khAstForLoop* for_loop); 466 | void khAstForLoop_delete(khAstForLoop* for_loop); 467 | khstring khAstForLoop_string(khAstForLoop* for_loop, char32_t* origin); 468 | 469 | 470 | typedef struct { 471 | kharray(khAstExpression) values; 472 | } khAstReturn; 473 | 474 | khAstReturn khAstReturn_copy(khAstReturn* return_v); 475 | void khAstReturn_delete(khAstReturn* return_v); 476 | khstring khAstReturn_string(khAstReturn* return_v, char32_t* origin); 477 | 478 | 479 | struct khAstStatement { 480 | char32_t* begin; 481 | char32_t* end; 482 | 483 | khAstStatementType type; 484 | union { 485 | khAstVariable variable; 486 | khAstExpression expression; 487 | 488 | khAstImport import_v; 489 | khAstInclude include; 490 | khAstFunction function; 491 | khAstClass class_v; 492 | khAstStruct struct_v; 493 | khAstEnum enum_v; 494 | khAstAlias alias; 495 | 496 | khAstIfBranch if_branch; 497 | khAstWhileLoop while_loop; 498 | khAstDoWhileLoop do_while_loop; 499 | khAstForLoop for_loop; 500 | khAstReturn return_v; 501 | }; 502 | }; 503 | 504 | khAstStatement khAstStatement_copy(khAstStatement* ast); 505 | void khAstStatement_delete(khAstStatement* ast); 506 | khstring khAstStatement_string(khAstStatement* ast, char32_t* origin); 507 | 508 | 509 | #ifdef __cplusplus 510 | } 511 | #endif 512 | -------------------------------------------------------------------------------- /include/kithare/core/error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | 19 | typedef enum { khErrorType_LEXER, khErrorType_PARSER, khErrorType_UNSPECIFIED } khErrorType; 20 | 21 | 22 | typedef struct { 23 | khErrorType type; 24 | khstring message; 25 | void* data; 26 | } khError; 27 | 28 | static inline khError khError_copy(khError* error) { 29 | return (khError){ 30 | .type = error->type, .message = khstring_copy(&error->message), .data = error->data}; 31 | } 32 | 33 | static inline void khError_delete(khError* error) { 34 | khstring_delete(&error->message); 35 | } 36 | 37 | 38 | void kh_raiseError(khError error); 39 | size_t kh_hasErrors(void); 40 | kharray(khError) * kh_getErrors(void); 41 | void kh_flushErrors(void); 42 | 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /include/kithare/core/info.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | 14 | #define kh_VERSION_MAJOR 0 // Can be from 0-9. 15 | #define kh_VERSION_MINOR 0 // Can be from 0-99. 16 | #define kh_VERSION_PATCH 1 // Can be from 0-99. 17 | #define kh_VERSION_TAGS "rc1" // Can be a[1-6], b[1-6], rc[1-2]. 18 | 19 | // Manually keep this macro updated, and don't use nested macros here 20 | #define kh_VERSION_STR "v0.0.1rc1" 21 | 22 | 23 | // These are done as macros so that it could be used in preprocessor if statements. 24 | #define khCOMPILER_EMSCRIPTEN 0 25 | #define khCOMPILER_MINGW64 1 26 | #define khCOMPILER_MINGW 2 27 | #define khCOMPILER_GCC_ARM64 3 28 | #define khCOMPILER_GCC_ARM 4 29 | #define khCOMPILER_GCC_POWPC 5 30 | #define khCOMPILER_GCC64 6 31 | #define khCOMPILER_GCC 7 32 | #define khCOMPILER_CLANG 8 33 | #define khCOMPILER_UNKNOWN 9 34 | 35 | #ifdef __EMSCRIPTEN__ 36 | #define kh_COMPILER khCOMPILER_EMSCRIPTEN 37 | #define kh_COMPILER_STR "Emscripten" 38 | 39 | #elif defined(__MINGW64__) 40 | #define kh_COMPILER khCOMPILER_MINGW64 41 | #define kh_COMPILER_STR "MinGW-x64" 42 | 43 | #elif defined(__MINGW32__) 44 | #define kh_COMPILER khCOMPILER_MINGW 45 | #define kh_COMPILER_STR "MinGW" 46 | 47 | #elif defined(__GNUC__) && __aarch64__ 48 | #define kh_COMPILER khCOMPILER_GCC_ARM64 49 | #define kh_COMPILER_STR "GCC-ARM64" 50 | 51 | #elif defined(__GNUC__) && __arm__ 52 | #define kh_COMPILER khCOMPILER_GCC_ARM 53 | #define kh_COMPILER_STR "GCC-ARM" 54 | 55 | #elif defined(__GNUC__) && __powerpc64__ 56 | #define kh_COMPILER khCOMPILER_GCC_POWPC 57 | #define kh_COMPILER_STR "GCC-PowerPC" 58 | 59 | #elif defined(__GNUC__) && __x86_64__ 60 | #define kh_COMPILER khCOMPILER_GCC64 61 | #define kh_COMPILER_STR "GCC-x64" 62 | 63 | #elif defined(__GNUC__) && __i386__ 64 | #define kh_COMPILER khCOMPILER_GCC 65 | #define kh_COMPILER_STR "GCC" 66 | 67 | #elif defined(__clang__) 68 | #define kh_COMPILER khCOMPILER_CLANG 69 | #define kh_COMPILER_STR "Clang" 70 | 71 | #else 72 | #define kh_COMPILER khCOMPILER_UNKNOWN 73 | #define kh_COMPILER_STR "Unknown" 74 | #endif 75 | 76 | 77 | // These are also done as macros. 78 | #define khPLATFORM_WEB 0 79 | #define khPLATFORM_WINDOWS64 1 80 | #define khPLATFORM_WINDOWS 2 81 | #define khPLATFORM_IOS 3 82 | #define khPLATFORM_MACOS 4 83 | #define khPLATFORM_APPLE 5 84 | #define khPLATFORM_ANDROID 6 85 | #define khPLATFORM_LINUX 7 86 | #define khPLATFORM_FREEBSD 8 87 | #define khPLATFORM_UNIX 9 88 | #define khPLATFORM_POSIX 10 89 | #define khPLATFORM_UNKNOWN 11 90 | 91 | #ifdef __EMSCRIPTEN__ 92 | #define kh_PLATFORM khPLATFORM_WEB 93 | #define kh_PLATFORM_STR "Web" 94 | 95 | #elif defined(_WIN64) 96 | #define kh_PLATFORM khPLATFORM_WINDOWS64 97 | #define kh_PLATFORM_STR "Windows (64 bit)" 98 | 99 | #elif defined(_WIN32) 100 | #define kh_PLATFORM khPLATFORM_WINDOWS 101 | #define kh_PLATFORM_STR "Windows" 102 | 103 | #elif defined(__APPLE__) 104 | #include 105 | #if TARGET_OS_IPHONE 106 | #define kh_PLATFORM khPLATFORM_IOS 107 | #define kh_PLATFORM_STR "iOS" 108 | 109 | #elif TARGET_OS_MAC 110 | #define kh_PLATFORM khPLATFORM_MACOS 111 | #define kh_PLATFORM_STR "MacOS" 112 | 113 | #else 114 | #define kh_PLATFORM khPLATFORM_APPLE 115 | #define kh_PLATFORM_STR "Unknown (Apple)" 116 | #endif 117 | 118 | #elif defined(__ANDROID__) 119 | #define kh_PLATFORM khPLATFORM_ANDROID 120 | #define kh_PLATFORM_STR "Android" 121 | 122 | #elif defined(__linux__) 123 | #define kh_PLATFORM khPLATFORM_LINUX 124 | #define kh_PLATFORM_STR "Linux" 125 | 126 | #elif defined(__FreeBSD__) 127 | #define kh_PLATFORM khPLATFORM_FREEBSD 128 | #define kh_PLATFORM_STR "FreeBSD" 129 | 130 | #elif defined(__unix__) 131 | #define kh_PLATFORM khPLATFORM_UNIX 132 | #define kh_PLATFORM_STR "Unix" 133 | 134 | #elif defined(__posix__) 135 | #define kh_PLATFORM khPLATFORM_POSIX 136 | #define kh_PLATFORM_STR "POSIX" 137 | 138 | #else 139 | #define kh_PLATFORM khPLATFORM_UNKNOWN 140 | #define kh_PLATFORM_STR "Unknown" 141 | #endif 142 | 143 | 144 | #ifdef __cplusplus 145 | } 146 | #endif 147 | -------------------------------------------------------------------------------- /include/kithare/core/lexer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | kharray(khToken) kh_lexicate(khstring* string); 23 | 24 | khToken kh_lexToken(char32_t** cursor); 25 | khToken kh_lexWord(char32_t** cursor); 26 | khToken kh_lexNumber(char32_t** cursor); 27 | khToken kh_lexSymbol(char32_t** cursor); 28 | 29 | char32_t kh_lexChar(char32_t** cursor, bool with_quotes, bool is_byte); 30 | khstring kh_lexString(char32_t** cursor, bool is_buffer); 31 | 32 | uint64_t kh_lexInt(char32_t** cursor, uint8_t base, size_t max_length, bool* had_overflowed); 33 | double kh_lexFloat(char32_t** cursor, uint8_t base); 34 | 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /include/kithare/core/parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | kharray(khAstStatement) kh_parse(khstring* string); 19 | 20 | khAstStatement kh_parseStatement(char32_t** cursor); 21 | khAstExpression kh_parseExpression(char32_t** cursor, bool ignore_newline, bool filter_type); 22 | 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /include/kithare/core/token.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | typedef enum { 21 | khTokenType_INVALID, 22 | khTokenType_EOF, 23 | khTokenType_NEWLINE, 24 | khTokenType_COMMENT, 25 | 26 | khTokenType_IDENTIFIER, 27 | khTokenType_KEYWORD, 28 | khTokenType_DELIMITER, 29 | khTokenType_OPERATOR, 30 | 31 | khTokenType_CHAR, 32 | khTokenType_STRING, 33 | khTokenType_BUFFER, 34 | 35 | khTokenType_BYTE, 36 | khTokenType_INTEGER, 37 | khTokenType_UINTEGER, 38 | khTokenType_FLOAT, 39 | khTokenType_DOUBLE, 40 | khTokenType_IFLOAT, 41 | khTokenType_IDOUBLE 42 | } khTokenType; 43 | 44 | khstring khTokenType_string(khTokenType type); 45 | 46 | 47 | typedef enum { 48 | khKeywordToken_IMPORT, 49 | khKeywordToken_INCLUDE, 50 | khKeywordToken_AS, 51 | khKeywordToken_DEF, 52 | khKeywordToken_CLASS, 53 | khKeywordToken_INHERITS, 54 | khKeywordToken_STRUCT, 55 | khKeywordToken_ENUM, 56 | khKeywordToken_ALIAS, 57 | 58 | khKeywordToken_REF, 59 | khKeywordToken_WILD, 60 | khKeywordToken_INCASE, 61 | khKeywordToken_STATIC, 62 | 63 | khKeywordToken_IF, 64 | khKeywordToken_ELIF, 65 | khKeywordToken_ELSE, 66 | khKeywordToken_FOR, 67 | khKeywordToken_IN, 68 | khKeywordToken_WHILE, 69 | khKeywordToken_DO, 70 | khKeywordToken_BREAK, 71 | khKeywordToken_CONTINUE, 72 | khKeywordToken_RETURN 73 | } khKeywordToken; 74 | 75 | khstring khKeywordToken_string(khKeywordToken keyword); 76 | 77 | 78 | typedef enum { 79 | khDelimiterToken_DOT, 80 | khDelimiterToken_COMMA, 81 | khDelimiterToken_COLON, 82 | khDelimiterToken_SEMICOLON, 83 | khDelimiterToken_EXCLAMATION, 84 | 85 | khDelimiterToken_PARENTHESIS_OPEN, 86 | khDelimiterToken_PARENTHESIS_CLOSE, 87 | khDelimiterToken_CURLY_BRACKET_OPEN, 88 | khDelimiterToken_CURLY_BRACKET_CLOSE, 89 | khDelimiterToken_SQUARE_BRACKET_OPEN, 90 | khDelimiterToken_SQUARE_BRACKET_CLOSE, 91 | 92 | khDelimiterToken_ARROW, 93 | khDelimiterToken_ELLIPSIS 94 | } khDelimiterToken; 95 | 96 | khstring khDelimiterToken_string(khDelimiterToken delimiter); 97 | 98 | 99 | typedef enum { 100 | khOperatorToken_ASSIGN, 101 | khOperatorToken_RANGE, 102 | 103 | khOperatorToken_ADD, 104 | khOperatorToken_SUB, 105 | khOperatorToken_MUL, 106 | khOperatorToken_DIV, 107 | khOperatorToken_MOD, 108 | khOperatorToken_DOT, 109 | khOperatorToken_POW, 110 | 111 | khOperatorToken_IP_ADD, 112 | khOperatorToken_IP_SUB, 113 | khOperatorToken_IP_MUL, 114 | khOperatorToken_IP_DIV, 115 | khOperatorToken_IP_MOD, 116 | khOperatorToken_IP_DOT, 117 | khOperatorToken_IP_POW, 118 | 119 | khOperatorToken_EQUAL, 120 | khOperatorToken_UNEQUAL, 121 | khOperatorToken_LESS, 122 | khOperatorToken_GREATER, 123 | khOperatorToken_LESS_EQUAL, 124 | khOperatorToken_GREATER_EQUAL, 125 | 126 | khOperatorToken_NOT, 127 | khOperatorToken_AND, 128 | khOperatorToken_OR, 129 | khOperatorToken_XOR, 130 | 131 | khOperatorToken_BIT_NOT, 132 | khOperatorToken_BIT_AND, 133 | khOperatorToken_BIT_OR, 134 | khOperatorToken_BIT_XOR = khOperatorToken_BIT_NOT, // Both uses `~`. 135 | khOperatorToken_BIT_LSHIFT = khOperatorToken_BIT_OR + 1, 136 | khOperatorToken_BIT_RSHIFT, 137 | 138 | khOperatorToken_IP_BIT_AND, 139 | khOperatorToken_IP_BIT_OR, 140 | khOperatorToken_IP_BIT_XOR, 141 | khOperatorToken_IP_BIT_LSHIFT, 142 | khOperatorToken_IP_BIT_RSHIFT 143 | } khOperatorToken; 144 | 145 | khstring khOperatorToken_string(khOperatorToken operator_v); 146 | 147 | 148 | typedef struct { 149 | char32_t* begin; 150 | char32_t* end; 151 | 152 | khTokenType type; 153 | union { 154 | khstring identifier; 155 | khKeywordToken keyword; 156 | khDelimiterToken delimiter; 157 | khOperatorToken operator_v; 158 | 159 | char32_t char_v; 160 | khstring string; 161 | khbuffer buffer; 162 | 163 | uint8_t byte; 164 | int64_t integer; 165 | uint64_t uinteger; 166 | float float_v; 167 | double double_v; 168 | float ifloat; 169 | double idouble; 170 | }; 171 | } khToken; 172 | 173 | khToken khToken_copy(khToken* token); 174 | void khToken_delete(khToken* token); 175 | khstring khToken_string(khToken* token, char32_t* origin); 176 | 177 | static inline khToken khToken_fromInvalid(char32_t* begin, char32_t* end) { 178 | return (khToken){.begin = begin, .end = end, .type = khTokenType_INVALID}; 179 | } 180 | 181 | static inline khToken khToken_fromEof(char32_t* begin, char32_t* end) { 182 | return (khToken){.begin = begin, .end = end, .type = khTokenType_EOF}; 183 | } 184 | 185 | static inline khToken khToken_fromNewline(char32_t* begin, char32_t* end) { 186 | return (khToken){.begin = begin, .end = end, .type = khTokenType_NEWLINE}; 187 | } 188 | 189 | static inline khToken khToken_fromComment(char32_t* begin, char32_t* end) { 190 | return (khToken){.begin = begin, .end = end, .type = khTokenType_COMMENT}; 191 | } 192 | 193 | static inline khToken khToken_fromIdentifier(khstring identifier, char32_t* begin, char32_t* end) { 194 | return (khToken){ 195 | .begin = begin, .end = end, .type = khTokenType_IDENTIFIER, .identifier = identifier}; 196 | } 197 | 198 | static inline khToken khToken_fromKeyword(khKeywordToken keyword, char32_t* begin, char32_t* end) { 199 | return (khToken){.begin = begin, .end = end, .type = khTokenType_KEYWORD, .keyword = keyword}; 200 | } 201 | 202 | static inline khToken khToken_fromDelimiter(khDelimiterToken delimiter, char32_t* begin, 203 | char32_t* end) { 204 | return (khToken){.begin = begin, .end = end, .type = khTokenType_DELIMITER, .delimiter = delimiter}; 205 | } 206 | 207 | static inline khToken khToken_fromOperator(khOperatorToken operator_v, char32_t* begin, char32_t* end) { 208 | return (khToken){ 209 | .begin = begin, .end = end, .type = khTokenType_OPERATOR, .operator_v = operator_v}; 210 | } 211 | 212 | static inline khToken khToken_fromChar(char32_t char_v, char32_t* begin, char32_t* end) { 213 | return (khToken){.begin = begin, .end = end, .type = khTokenType_CHAR, .char_v = char_v}; 214 | } 215 | 216 | static inline khToken khToken_fromString(khstring string, char32_t* begin, char32_t* end) { 217 | return (khToken){.begin = begin, .end = end, .type = khTokenType_STRING, .string = string}; 218 | } 219 | 220 | static inline khToken khToken_fromBuffer(khbuffer buffer, char32_t* begin, char32_t* end) { 221 | return (khToken){.begin = begin, .end = end, .type = khTokenType_BUFFER, .buffer = buffer}; 222 | } 223 | 224 | static inline khToken khToken_fromByte(uint8_t byte, char32_t* begin, char32_t* end) { 225 | return (khToken){.begin = begin, .end = end, .type = khTokenType_BYTE, .byte = byte}; 226 | } 227 | 228 | static inline khToken khToken_fromInteger(int64_t integer, char32_t* begin, char32_t* end) { 229 | return (khToken){.begin = begin, .end = end, .type = khTokenType_INTEGER, .integer = integer}; 230 | } 231 | 232 | static inline khToken khToken_fromUinteger(uint64_t uinteger, char32_t* begin, char32_t* end) { 233 | return (khToken){.begin = begin, .end = end, .type = khTokenType_UINTEGER, .uinteger = uinteger}; 234 | } 235 | 236 | static inline khToken khToken_fromFloat(float float_v, char32_t* begin, char32_t* end) { 237 | return (khToken){.begin = begin, .end = end, .type = khTokenType_FLOAT, .float_v = float_v}; 238 | } 239 | 240 | static inline khToken khToken_fromDouble(double double_v, char32_t* begin, char32_t* end) { 241 | return (khToken){.begin = begin, .end = end, .type = khTokenType_DOUBLE, .double_v = double_v}; 242 | } 243 | 244 | static inline khToken khToken_fromIfloat(float ifloat, char32_t* begin, char32_t* end) { 245 | return (khToken){.begin = begin, .end = end, .type = khTokenType_IFLOAT, .ifloat = ifloat}; 246 | } 247 | 248 | static inline khToken khToken_fromIdouble(double idouble, char32_t* begin, char32_t* end) { 249 | return (khToken){.begin = begin, .end = end, .type = khTokenType_IDOUBLE, .idouble = idouble}; 250 | } 251 | 252 | 253 | #ifdef __cplusplus 254 | } 255 | #endif 256 | -------------------------------------------------------------------------------- /include/kithare/lib/ansi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | 14 | #define kh_ANSI_FG_BLACK "\033[30m" 15 | #define kh_ANSI_FG_RED "\033[31m" 16 | #define kh_ANSI_FG_GREEN "\033[32m" 17 | #define kh_ANSI_FG_YELLOW "\033[33m" 18 | #define kh_ANSI_FG_BLUE "\033[34m" 19 | #define kh_ANSI_FG_MAGENTA "\033[35m" 20 | #define kh_ANSI_FG_CYAN "\033[36m" 21 | #define kh_ANSI_FG_WHITE "\033[37m" 22 | 23 | #define kh_ANSI_BG_BLACK "\033[40m" 24 | #define kh_ANSI_BG_RED "\033[41m" 25 | #define kh_ANSI_BG_GREEN "\033[42m" 26 | #define kh_ANSI_BG_YELLOW "\033[43m" 27 | #define kh_ANSI_BG_BLUE "\033[44m" 28 | #define kh_ANSI_BG_MAGENTA "\033[45m" 29 | #define kh_ANSI_BG_CYAN "\033[46m" 30 | #define kh_ANSI_BG_WHITE "\033[47m" 31 | 32 | #define kh_ANSI_FG_RGB(R, G, B) "\033[38;2;" #R ";" #G ";" #B "m" 33 | #define kh_ANSI_BG_RGB(R, G, B) "\033[48;2;" #R ";" #G ";" #B "m" 34 | 35 | #define kh_ANSI_RESET "\033[0m" 36 | #define kh_ANSI_BOLD "\033[1m" 37 | #define kh_ANSI_DIM "\033[2m" 38 | #define kh_ANSI_ITALIC "\033[3m" 39 | #define kh_ANSI_UNDERLINE "\033[4m" 40 | #define kh_ANSI_BLINKING "\033[5m" 41 | #define kh_ANSI_INVERSE "\033[7m" 42 | #define kh_ANSI_INVISIBLE "\033[8m" 43 | #define kh_ANSI_STRIKETHROUGH "\033[9m" 44 | 45 | #define kh_ANSI_HIDE_CURSOR "\033[?25l" 46 | #define kh_ANSI_SHOW_CURSOR "\033[?25h" 47 | #define kh_ANSI_SAVE_CURSOR "\033[s" 48 | #define kh_ANSI_RESTORE_CURSOR "\033[u" 49 | #define kh_ANSI_MOVE_UP_CURSOR(X) "\033[" #X "A" 50 | #define kh_ANSI_MOVE_DOWN_CURSOR(X) "\033[" #X "B" 51 | #define kh_ANSI_MOVE_RIGHT_CURSOR(X) "\033[" #X "C" 52 | #define kh_ANSI_MOVE_LEFT_CURSOR(X) "\033[" #X "D" 53 | 54 | #define kh_ANSI_CLEAR_SCREEN "\033[2J" 55 | #define kh_ANSI_CLEAR_LINE "\033[2K" 56 | 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /include/kithare/lib/array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | typedef struct { 19 | size_t type_size; 20 | void (*deleter)(void*); 21 | size_t size; 22 | size_t reserved; 23 | } _kharrayHeader; 24 | 25 | 26 | // Getter macros from the header 27 | #define kharray(TYPE) TYPE* // Type alias 28 | #define _kharray_header(ARRAY) (((_kharrayHeader*)(*ARRAY))[-1]) 29 | #define _kharray_typeSize(ARRAY) (_kharray_header(ARRAY).type_size) 30 | #define _kharray_deleter(ARRAY) (_kharray_header(ARRAY).deleter) 31 | #define kharray_size(ARRAY) (_kharray_header(ARRAY).size) 32 | #define kharray_reserved(ARRAY) (_kharray_header(ARRAY).reserved) 33 | 34 | // Verifies that the argument given is a pointer to a pointer (Known as a pointer to an array; e.g: 35 | // int**, char**), then casts it into a void** 36 | #define _kharray_verify(PTR) \ 37 | ({ \ 38 | typeof(PTR) __kh_ptr = PTR; \ 39 | typeof(**__kh_ptr) __kh_dptr __attribute__((unused)) = **__kh_ptr; \ 40 | (void**)__kh_ptr; \ 41 | }) 42 | 43 | 44 | #define kharray_new(TYPE, DELETER) (TYPE*)_kharray_new(sizeof(TYPE), (void (*)(void*))(DELETER)) 45 | static inline void* _kharray_new(size_t type_size, void (*deleter)(void*)) { 46 | // Don't forget to allocate an extra null-terminator space in case it's a string 47 | void* array = calloc(sizeof(_kharrayHeader) + type_size, 1); 48 | *(_kharrayHeader*)array = 49 | (_kharrayHeader){.type_size = type_size, .size = 0, .reserved = 0, .deleter = deleter}; 50 | return array + sizeof(_kharrayHeader); 51 | } 52 | 53 | #define kharray_copy(ARRAY, COPIER) \ 54 | ({ \ 55 | typeof(ARRAY) __kh_array_ptr = ARRAY; \ 56 | typeof(*__kh_array_ptr) __kh_array = *__kh_array_ptr; \ 57 | \ 58 | /* The copied array */ \ 59 | typeof(__kh_array) __kh_copy = \ 60 | calloc(sizeof(_kharrayHeader) + \ 61 | _kharray_typeSize(__kh_array_ptr) * (kharray_size(__kh_array_ptr) + 1), \ 62 | 1); \ 63 | \ 64 | /* Placing the array header and fitting the reserve count, then offsetting the copy */ \ 65 | *(_kharrayHeader*)__kh_copy = _kharray_header(__kh_array_ptr); \ 66 | ((_kharrayHeader*)__kh_copy)->reserved = kharray_size(__kh_array_ptr); \ 67 | __kh_copy = (typeof(__kh_array))((_kharrayHeader*)__kh_copy + 1); \ 68 | \ 69 | /* Call the copy constructor of each element, unless it's NULL */ \ 70 | if (COPIER == NULL) { \ 71 | memcpy(__kh_copy, __kh_array, \ 72 | _kharray_typeSize(__kh_array_ptr) * kharray_size(__kh_array_ptr)); \ 73 | } \ 74 | else { \ 75 | for (size_t __kh_i = 0; __kh_i < kharray_size(__kh_array_ptr); __kh_i++) { \ 76 | __kh_copy[__kh_i] = \ 77 | ((typeof (*__kh_array) (*)(typeof(__kh_array)))(COPIER))(&__kh_array[__kh_i]); \ 78 | } \ 79 | } \ 80 | \ 81 | __kh_copy; \ 82 | }) 83 | 84 | #define kharray_delete(ARRAY) _kharray_delete(_kharray_verify(ARRAY)) 85 | #define kharray_arrayDeleter(TYPE) ((void (*)(TYPE**))_kharray_delete) 86 | static inline void _kharray_delete(void** array) { 87 | if (_kharray_deleter(array) != NULL) { 88 | // Increment by type size 89 | for (size_t i = 0; i < _kharray_typeSize(array) * kharray_size(array); 90 | i += _kharray_typeSize(array)) { 91 | _kharray_deleter(array)(*array + i); 92 | } 93 | } 94 | 95 | // Don't forget to undo the header offset before freeing it 96 | free(*array - sizeof(_kharrayHeader)); 97 | *array = NULL; 98 | } 99 | 100 | #define kharray_reserve(ARRAY, SIZE) _kharray_reserve(_kharray_verify(ARRAY), SIZE) 101 | static inline void _kharray_reserve(void** array, size_t size) { 102 | if (kharray_reserved(array) >= size) { 103 | return; 104 | } 105 | 106 | // Also, don't forget the null-terminator space 107 | void* expanded_array = calloc(sizeof(_kharrayHeader) + _kharray_typeSize(array) * (size + 1), 1); 108 | *(_kharrayHeader*)expanded_array = _kharray_header(array); 109 | memcpy(expanded_array + sizeof(_kharrayHeader), *array, 110 | _kharray_typeSize(array) * kharray_size(array)); 111 | // Undo offset, free! 112 | free(*array - sizeof(_kharrayHeader)); 113 | 114 | ((_kharrayHeader*)expanded_array)->reserved = size; 115 | *array = expanded_array + sizeof(_kharrayHeader); 116 | } 117 | 118 | #define kharray_fit(ARRAY) _kharray_fit(_kharray_verify(ARRAY)); 119 | static inline void _kharray_fit(void** array) { 120 | // Pretty much the same implementation of `_kharray_reserve` but with this part different 121 | if (kharray_reserved(array) == kharray_size(array)) { 122 | return; 123 | } 124 | 125 | void* shrunk_array = 126 | calloc(sizeof(_kharrayHeader) + _kharray_typeSize(array) * (kharray_size(array) + 1), 1); 127 | *(_kharrayHeader*)shrunk_array = _kharray_header(array); 128 | memcpy(shrunk_array + sizeof(_kharrayHeader), *array, 129 | _kharray_typeSize(array) * kharray_size(array)); 130 | free(*array - sizeof(_kharrayHeader)); 131 | 132 | ((_kharrayHeader*)shrunk_array)->reserved = kharray_size(array); 133 | *array = shrunk_array + sizeof(_kharrayHeader); 134 | } 135 | 136 | #define kharray_pop(ARRAY, ITEMS) _kharray_pop(_kharray_verify(ARRAY), ITEMS) 137 | static inline void _kharray_pop(void** array, size_t items) { 138 | // If it's trying to pop more items than the array actually has, cap it 139 | items = items > kharray_size(array) ? kharray_size(array) : items; 140 | 141 | if (_kharray_deleter(array) != NULL) { 142 | for (size_t i = items; i > 0; i--) { 143 | _kharray_deleter(array)(*array + _kharray_typeSize(array) * (kharray_size(array) - i)); 144 | } 145 | } 146 | 147 | // Clean after yourself 148 | memset(*array + _kharray_typeSize(array) * (kharray_size(array) - items), 0, 149 | _kharray_typeSize(array) * items); 150 | kharray_size(array) -= items; 151 | } 152 | 153 | #define kharray_reverse(ARRAY) _kharray_reverse(_kharray_verify(ARRAY)) 154 | static inline void _kharray_reverse(void** array) { 155 | for (size_t i = 0; i < kharray_size(array) / 2; i++) { 156 | char tmp[_kharray_typeSize(array)]; 157 | 158 | // front -> tmp 159 | memcpy(tmp, *array + i * _kharray_typeSize(array), _kharray_typeSize(array)); 160 | 161 | // back -> front 162 | memcpy(*array + i * _kharray_typeSize(array), 163 | *array + (kharray_size(array) - i - 1) * _kharray_typeSize(array), 164 | _kharray_typeSize(array)); 165 | 166 | // tmp -> back 167 | memcpy(*array + (kharray_size(array) - i - 1) * _kharray_typeSize(array), tmp, 168 | _kharray_typeSize(array)); 169 | } 170 | } 171 | 172 | #define kharray_memory(ARRAY, PTR, SIZE, COPIER) \ 173 | { \ 174 | typeof(ARRAY) __kh_array_ptr = ARRAY; \ 175 | typeof(PTR) __kh_ptr = PTR; \ 176 | size_t __kh_size = SIZE; \ 177 | \ 178 | /* If the reserved memory isn't enough, try to allocate more by *2 of the existing memory */ \ 179 | if (kharray_size(__kh_array_ptr) + __kh_size > kharray_reserved(__kh_array_ptr)) { \ 180 | size_t __kh_exsize = kharray_size(__kh_array_ptr) ? kharray_size(__kh_array_ptr) * 2 : 2; \ 181 | kharray_reserve(__kh_array_ptr, __kh_exsize < kharray_size(__kh_array_ptr) + __kh_size \ 182 | ? kharray_size(__kh_array_ptr) + __kh_size \ 183 | : __kh_exsize); \ 184 | } \ 185 | \ 186 | typeof(*__kh_array_ptr) __kh_array = *__kh_array_ptr; \ 187 | \ 188 | /* Call the copy constructor of each element, unless it's NULL */ \ 189 | if (COPIER == NULL) { \ 190 | memcpy(__kh_array + kharray_size(__kh_array_ptr), __kh_ptr, \ 191 | _kharray_typeSize(__kh_array_ptr) * __kh_size); \ 192 | } \ 193 | else { \ 194 | for (size_t __kh_i = 0; __kh_i < __kh_size; __kh_i++) { \ 195 | __kh_array[kharray_size(__kh_array_ptr) + __kh_i] = \ 196 | ((typeof (*__kh_array) (*)(typeof(__kh_array)))(COPIER))(__kh_ptr + __kh_i); \ 197 | } \ 198 | } \ 199 | \ 200 | kharray_size(__kh_array_ptr) += __kh_size; \ 201 | } 202 | 203 | #define kharray_append(ARRAY, ITEM) \ 204 | { \ 205 | typeof(ARRAY) ___kh_array_ptr = ARRAY; \ 206 | typeof(*___kh_array_ptr) ___kh_array = *___kh_array_ptr; \ 207 | typeof(*___kh_array) ___kh_item = ITEM; \ 208 | \ 209 | kharray_memory(___kh_array_ptr, &___kh_item, 1, NULL); \ 210 | } 211 | 212 | #define kharray_concatenate(ARRAY, OTHER, COPIER) \ 213 | kharray_memory(ARRAY, *(OTHER), kharray_size(OTHER), COPIER) 214 | 215 | 216 | #ifdef __cplusplus 217 | } 218 | #endif 219 | -------------------------------------------------------------------------------- /include/kithare/lib/buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | 15 | #include "array.h" 16 | #include "string.h" 17 | 18 | 19 | typedef kharray(uint8_t) khbuffer; 20 | 21 | 22 | static inline khbuffer khbuffer_new(const char* cstring) { 23 | khbuffer buffer = kharray_new(uint8_t, NULL); 24 | kharray_memory(&buffer, (uint8_t*)cstring, strlen(cstring), NULL); 25 | return buffer; 26 | } 27 | 28 | static inline khbuffer khbuffer_copy(khbuffer* buffer) { 29 | return kharray_copy(buffer, NULL); 30 | } 31 | 32 | static inline void khbuffer_delete(khbuffer* buffer) { 33 | kharray_delete(buffer); 34 | } 35 | 36 | static inline size_t khbuffer_size(khbuffer* buffer) { 37 | return kharray_size(buffer); 38 | } 39 | 40 | static inline size_t khbuffer_reserved(khbuffer* buffer) { 41 | return kharray_reserved(buffer); 42 | } 43 | 44 | static inline void khbuffer_reserve(khbuffer* buffer, size_t size) { 45 | kharray_reserve(buffer, size); 46 | } 47 | 48 | static inline void khbuffer_fit(khbuffer* buffer) { 49 | kharray_fit(buffer); 50 | } 51 | 52 | static inline void khbuffer_pop(khbuffer* buffer, size_t items) { 53 | kharray_pop(buffer, items); 54 | } 55 | 56 | static inline void khbuffer_reverse(khbuffer* buffer) { 57 | kharray_reverse(buffer); 58 | } 59 | 60 | static inline void khbuffer_append(khbuffer* buffer, uint8_t chr) { 61 | kharray_append(buffer, chr); 62 | } 63 | 64 | static inline void khbuffer_concatenate(khbuffer* buffer, khbuffer* other) { 65 | kharray_concatenate(buffer, other, NULL); 66 | } 67 | 68 | static inline void khbuffer_concatenateCstring(khbuffer* buffer, const char* cstring) { 69 | kharray_memory(buffer, (uint8_t*)cstring, strlen(cstring), NULL); 70 | } 71 | 72 | static inline bool khbuffer_equal(khbuffer* a, khbuffer* b) { 73 | if (khbuffer_size(a) != khbuffer_size(b)) { 74 | return false; 75 | } 76 | 77 | for (size_t i = 0; i < khbuffer_size(a); i++) { 78 | if ((*a)[i] != (*b)[i]) { 79 | return false; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | static inline bool khbuffer_equalCstring(khbuffer* buffer, const char* cstring) { 87 | size_t length = strlen(cstring); 88 | if (khbuffer_size(buffer) != length) { 89 | return false; 90 | } 91 | 92 | for (size_t i = 0; i < length; i++) { 93 | if ((*buffer)[i] != cstring[i]) { 94 | return false; 95 | } 96 | } 97 | 98 | return true; 99 | } 100 | 101 | static inline bool khbuffer_startsWith(khbuffer* buffer, khbuffer* prefix) { 102 | if (khbuffer_size(buffer) < khbuffer_size(prefix)) { 103 | return false; 104 | } 105 | 106 | for (size_t i = 0; i < khbuffer_size(prefix); i++) { 107 | if ((*buffer)[i] != (*prefix)[i]) { 108 | return false; 109 | } 110 | } 111 | 112 | return true; 113 | } 114 | 115 | static inline bool khbuffer_startsWithCstring(khbuffer* buffer, const char* cstring) { 116 | size_t length = strlen(cstring); 117 | if (khbuffer_size(buffer) < length) { 118 | return false; 119 | } 120 | 121 | for (size_t i = 0; i < length; i++) { 122 | if ((*buffer)[i] != cstring[i]) { 123 | return false; 124 | } 125 | } 126 | 127 | return true; 128 | } 129 | 130 | static inline bool khbuffer_endsWith(khbuffer* buffer, khbuffer* suffix) { 131 | if (khbuffer_size(buffer) < khbuffer_size(suffix)) { 132 | return false; 133 | } 134 | 135 | for (size_t i = 0; i < khbuffer_size(suffix); i++) { 136 | if ((*buffer)[khbuffer_size(buffer) - khbuffer_size(suffix) + i] != (*suffix)[i]) { 137 | return false; 138 | } 139 | } 140 | 141 | return true; 142 | } 143 | 144 | static inline bool khbuffer_endsWithCstring(khbuffer* buffer, const char* cstring) { 145 | size_t length = strlen(cstring); 146 | if (khbuffer_size(buffer) < length) { 147 | return false; 148 | } 149 | 150 | for (size_t i = 0; i < length; i++) { 151 | if ((*buffer)[khbuffer_size(buffer) - length + i] != cstring[i]) { 152 | return false; 153 | } 154 | } 155 | 156 | return true; 157 | } 158 | 159 | static inline khstring khbuffer_quote(khbuffer* buffer) { 160 | khstring quoted_buffer = khstring_new(U"\""); 161 | 162 | for (uint8_t* byte = *buffer; byte < *buffer + khbuffer_size(buffer); byte++) { 163 | if (*byte == '\'') { 164 | khstring_append("ed_buffer, U'\''); 165 | } 166 | else { 167 | khstring escaped = kh_escapeChar(*byte); 168 | khstring_concatenate("ed_buffer, &escaped); 169 | khstring_delete(&escaped); 170 | } 171 | } 172 | 173 | khstring_append("ed_buffer, U'\"'); 174 | return quoted_buffer; 175 | } 176 | 177 | 178 | #ifdef __cplusplus 179 | } 180 | #endif 181 | -------------------------------------------------------------------------------- /include/kithare/lib/io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef _WIN32 19 | #include 20 | #endif 21 | 22 | #include "array.h" 23 | #include "buffer.h" 24 | #include "string.h" 25 | 26 | 27 | static inline void kh_put(khstring* string, FILE* stream) { 28 | khbuffer buffer = kh_encodeUtf8(string); 29 | fputs((char*)buffer, stream); 30 | khbuffer_delete(&buffer); 31 | } 32 | 33 | static inline void kh_putln(khstring* string, FILE* stream) { 34 | khbuffer buffer = kh_encodeUtf8(string); 35 | fprintf(stream, "%s\n", (char*)buffer); 36 | khbuffer_delete(&buffer); 37 | } 38 | 39 | static inline khbuffer kh_readFile(khstring* file_name, bool* success) { 40 | khbuffer buffer = khbuffer_new(""); 41 | 42 | #ifdef _WIN32 43 | char16_t* char16_file_name = (char16_t*)calloc(khstring_size(file_name) + 1, sizeof(char16_t)); 44 | for (size_t i = 0; i < khstring_size(file_name); i++) { 45 | char16_file_name[i] = (char16_t)(*file_name)[i]; 46 | } 47 | 48 | FILE* file = _wfopen(char16_file_name, L"rb"); 49 | free(char16_file_name); 50 | #else 51 | khbuffer utf8_file_name = kh_encodeUtf8(file_name); 52 | FILE* file = fopen((char*)utf8_file_name, "rb"); 53 | khbuffer_delete(&utf8_file_name); 54 | #endif 55 | 56 | if (file != NULL) { 57 | fseek(file, 0, SEEK_END); 58 | khbuffer_reserve(&buffer, ftell(file)); 59 | rewind(file); 60 | 61 | int chr; 62 | while ((chr = fgetc(file)) != EOF) { 63 | khbuffer_append(&buffer, chr); 64 | } 65 | 66 | *success = true; 67 | fclose(file); 68 | } 69 | else { 70 | *success = false; 71 | } 72 | 73 | return buffer; 74 | } 75 | 76 | 77 | #ifdef __cplusplus 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /include/kithare/lib/string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #pragma once 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // Avaxar: Apple sucks 20 | #ifdef __APPLE__ 21 | typedef __CHAR16_TYPE__ char16_t; 22 | typedef __CHAR32_TYPE__ char32_t; 23 | #else 24 | #include 25 | #endif 26 | 27 | #include "array.h" 28 | 29 | 30 | typedef kharray(uint8_t) khbuffer; 31 | typedef kharray(char32_t) khstring; 32 | 33 | 34 | static inline khstring khstring_new(const char32_t* cstring) { 35 | size_t size = 0; 36 | for (; cstring[size] != U'\0'; size++) {} 37 | 38 | khstring string = kharray_new(char32_t, NULL); 39 | kharray_memory(&string, (char32_t*)cstring, size, NULL); 40 | 41 | return string; 42 | } 43 | 44 | static inline khstring khstring_copy(khstring* string) { 45 | return kharray_copy(string, NULL); 46 | } 47 | 48 | static inline void khstring_delete(khstring* string) { 49 | kharray_delete(string); 50 | } 51 | 52 | static inline size_t khstring_size(khstring* string) { 53 | return kharray_size(string); 54 | } 55 | 56 | static inline size_t khstring_reserved(khstring* string) { 57 | return kharray_reserved(string); 58 | } 59 | 60 | static inline void khstring_reserve(khstring* string, size_t size) { 61 | kharray_reserve(string, size); 62 | } 63 | 64 | static inline void khstring_fit(khstring* string) { 65 | kharray_fit(string); 66 | } 67 | 68 | static inline void khstring_pop(khstring* string, size_t items) { 69 | kharray_pop(string, items); 70 | } 71 | 72 | static inline void khstring_reverse(khstring* string) { 73 | kharray_reverse(string); 74 | } 75 | 76 | static inline void khstring_append(khstring* string, char32_t chr) { 77 | kharray_append(string, chr); 78 | } 79 | 80 | static inline void khstring_concatenate(khstring* string, khstring* other) { 81 | kharray_concatenate(string, other, NULL); 82 | } 83 | 84 | static inline void khstring_concatenateCstring(khstring* string, const char32_t* cstring) { 85 | size_t size = 0; 86 | for (; cstring[size] != U'\0'; size++) {} 87 | kharray_memory(string, (char32_t*)cstring, size, NULL); 88 | } 89 | 90 | static inline bool khstring_equal(khstring* a, khstring* b) { 91 | if (khstring_size(a) != khstring_size(b)) { 92 | return false; 93 | } 94 | 95 | for (size_t i = 0; i < khstring_size(a); i++) { 96 | if ((*a)[i] != (*b)[i]) { 97 | return false; 98 | } 99 | } 100 | 101 | return true; 102 | } 103 | 104 | static inline bool khstring_equalCstring(khstring* a, const char32_t* b) { 105 | size_t length = 0; 106 | for (; b[length] != '\0'; length++) {} 107 | 108 | if (khstring_size(a) != length) { 109 | return false; 110 | } 111 | 112 | for (size_t i = 0; i < khstring_size(a); i++) { 113 | if ((*a)[i] != b[i]) { 114 | return false; 115 | } 116 | } 117 | 118 | return true; 119 | } 120 | 121 | static inline bool khstring_startsWith(khstring* string, khstring* prefix) { 122 | if (khstring_size(string) < khstring_size(prefix)) { 123 | return false; 124 | } 125 | 126 | for (size_t i = 0; i < khstring_size(prefix); i++) { 127 | if ((*string)[i] != (*prefix)[i]) { 128 | return false; 129 | } 130 | } 131 | 132 | return true; 133 | } 134 | 135 | static inline bool khstring_startsWithCstring(khstring* string, const char32_t* prefix) { 136 | size_t length = 0; 137 | for (; prefix[length] != '\0'; length++) {} 138 | 139 | if (khstring_size(string) < length) { 140 | return false; 141 | } 142 | 143 | for (size_t i = 0; i < length; i++) { 144 | if ((*string)[i] != prefix[i]) { 145 | return false; 146 | } 147 | } 148 | 149 | return true; 150 | } 151 | 152 | static inline bool khstring_endsWith(khstring* string, khstring* suffix) { 153 | if (khstring_size(string) < khstring_size(suffix)) { 154 | return false; 155 | } 156 | 157 | for (size_t i = 0; i < khstring_size(suffix); i++) { 158 | if ((*string)[khstring_size(string) - khstring_size(suffix) + i] != (*suffix)[i]) { 159 | return false; 160 | } 161 | } 162 | 163 | return true; 164 | } 165 | 166 | static inline bool khstring_endsWithCstring(khstring* string, const char32_t* suffix) { 167 | size_t length = 0; 168 | for (; suffix[length] != '\0'; length++) {} 169 | 170 | if (khstring_size(string) < length) { 171 | return false; 172 | } 173 | 174 | for (size_t i = 0; i < length; i++) { 175 | if ((*string)[khstring_size(string) - length + i] != suffix[i]) { 176 | return false; 177 | } 178 | } 179 | 180 | return true; 181 | } 182 | 183 | static inline khstring kh_uintToString(uint64_t uint_v, uint8_t base) { 184 | khstring string = khstring_new(U""); 185 | 186 | uint64_t value = uint_v; 187 | while (value > 0) { 188 | uint8_t digit = value % base; 189 | khstring_append(&string, (digit < 10 ? U'0' + digit : U'A' + digit - 10)); 190 | value /= base; 191 | } 192 | 193 | if (khstring_size(&string) == 0) { 194 | khstring_append(&string, U'0'); 195 | } 196 | 197 | kharray_reverse(&string); 198 | return string; 199 | } 200 | 201 | static inline khstring kh_intToString(int64_t int_v, uint8_t base) { 202 | khstring string = khstring_new(U""); 203 | 204 | if (int_v < 0) { 205 | khstring_append(&string, U'-'); 206 | int_v *= -1; 207 | } 208 | 209 | khstring str = kh_uintToString(int_v, base); 210 | khstring_concatenate(&string, &str); 211 | khstring_delete(&str); 212 | 213 | return string; 214 | } 215 | 216 | static inline khstring kh_floatToString(double floating, uint8_t precision, uint8_t base) { 217 | khstring string = khstring_new(U""); 218 | 219 | if (floating < 0) { 220 | khstring_append(&string, U'-'); 221 | floating *= -1; 222 | } 223 | 224 | if (isinf(floating)) { 225 | khstring_concatenateCstring(&string, U"inf"); 226 | } 227 | else if (isnan(floating)) { 228 | khstring_concatenateCstring(&string, U"nan"); 229 | } 230 | else { 231 | double value = floating; 232 | while (value >= 1) { 233 | uint8_t digit = (uint8_t)fmod(value, base); 234 | khstring_append(&string, (digit < 10 ? U'0' + digit : U'A' + digit - 10)); 235 | value /= base; 236 | } 237 | 238 | kharray_reverse(&string); 239 | if (khstring_size(&string) == 0) { 240 | khstring_append(&string, U'0'); 241 | } 242 | 243 | if (precision > 0) { 244 | khstring_append(&string, U'.'); 245 | 246 | value = floating; 247 | for (uint8_t i = 0; i < precision; i++) { 248 | value *= base; 249 | uint8_t digit = (uint8_t)fmod(value, base); 250 | khstring_append(&string, (digit < 10 ? U'0' + digit : U'A' + digit - 10)); 251 | } 252 | } 253 | } 254 | 255 | return string; 256 | } 257 | 258 | static inline char32_t kh_utf8(uint8_t** cursor) { 259 | // Pass ASCII characters 260 | if ((uint8_t)(**cursor) < 128) { 261 | return *(*cursor)++; 262 | } 263 | 264 | char32_t chr = 0; 265 | uint8_t continuation = 0; 266 | 267 | if ((**cursor & 0b11100000) == 0b11000000) { 268 | chr = **cursor & 0b00011111; 269 | continuation = 1; 270 | } 271 | else if ((**cursor & 0b11110000) == 0b11100000) { 272 | chr = **cursor & 0b00001111; 273 | continuation = 2; 274 | } 275 | else if ((**cursor & 0b11111000) == 0b11110000) { 276 | chr = **cursor & 0b00000111; 277 | continuation = 3; 278 | } 279 | else if ((**cursor & 0b11111100) == 0b11111000) { 280 | chr = **cursor & 0b00000011; 281 | continuation = 4; 282 | } 283 | else if ((**cursor & 0b11111110) == 0b11111100) { 284 | chr = **cursor & 0b00000001; 285 | continuation = 5; 286 | } 287 | 288 | for ((*cursor)++; continuation > 0; continuation--, (*cursor)++) { 289 | if ((**cursor & 0b11000000) != 0b10000000) { 290 | return -1; 291 | } 292 | 293 | chr = (chr << 6) | (**cursor & 0b00111111); 294 | } 295 | 296 | return chr; 297 | } 298 | 299 | static inline khbuffer kh_encodeUtf8(khstring* string) { 300 | khbuffer buffer = kharray_new(uint8_t, NULL); // Can't use `khbuffer_new` 301 | kharray_reserve(&buffer, khstring_size(string)); 302 | 303 | // Can't use `khbuffer_append` 304 | for (char32_t* chr = *string; chr < *string + khstring_size(string); chr++) { 305 | if (*chr > 0xFFFF) { 306 | kharray_append(&buffer, 0b11110000 | (uint8_t)(0b00000111 & (*chr >> 18))); 307 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & (*chr >> 12))); 308 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & (*chr >> 6))); 309 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & *chr)); 310 | } 311 | else if (*chr > 0x7FF) { 312 | kharray_append(&buffer, 0b11100000 | (uint8_t)(0b00001111 & (*chr >> 12))); 313 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & (*chr >> 6))); 314 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & *chr)); 315 | } 316 | else if (*chr > 0x7F) { 317 | kharray_append(&buffer, 0b11000000 | (uint8_t)(0b00011111 & (*chr >> 6))); 318 | kharray_append(&buffer, 0b10000000 | (uint8_t)(0b00111111 & *chr)); 319 | } 320 | else { 321 | kharray_append(&buffer, *chr); 322 | } 323 | } 324 | 325 | return buffer; 326 | } 327 | 328 | static inline khstring kh_decodeUtf8(khbuffer* buffer) { 329 | khstring string = khstring_new(U""); 330 | kharray_reserve(&string, kharray_size(buffer)); // Can't use khbuffer_size 331 | 332 | uint8_t* cursor = *buffer; 333 | while (cursor != *buffer + kharray_size(buffer)) { 334 | khstring_append(&string, kh_utf8(&cursor)); 335 | } 336 | 337 | return string; 338 | } 339 | 340 | static inline khstring kh_escapeChar(char32_t chr) { 341 | switch (chr) { 342 | // Regular single character escapes 343 | case U'\0': 344 | return khstring_new(U"\\0"); 345 | case U'\n': 346 | return khstring_new(U"\\n"); 347 | case U'\r': 348 | return khstring_new(U"\\r"); 349 | case U'\t': 350 | return khstring_new(U"\\t"); 351 | case U'\v': 352 | return khstring_new(U"\\v"); 353 | case U'\b': 354 | return khstring_new(U"\\b"); 355 | case U'\a': 356 | return khstring_new(U"\\a"); 357 | case U'\f': 358 | return khstring_new(U"\\f"); 359 | case U'\\': 360 | return khstring_new(U"\\\\"); 361 | case U'\'': 362 | return khstring_new(U"\\\'"); 363 | case U'\"': 364 | return khstring_new(U"\\\""); 365 | 366 | default: { 367 | khstring string = khstring_new(U""); 368 | 369 | if (iswprint(chr)) { 370 | khstring_append(&string, chr); 371 | return string; 372 | } 373 | 374 | uint8_t chars; 375 | if (chr < 0x100) { 376 | khstring_concatenateCstring(&string, U"\\x"); 377 | chars = 2; 378 | } 379 | else if (chr < 0x10000) { 380 | khstring_concatenateCstring(&string, U"\\u"); 381 | chars = 4; 382 | } 383 | else { 384 | khstring_concatenateCstring(&string, U"\\U"); 385 | chars = 8; 386 | } 387 | 388 | // Fills the placeholder zeroes \x0AAA 389 | khstring hex = kh_uintToString(chr, 16); 390 | for (uint8_t i = 0; i < (uint8_t)khstring_size(&hex) - chars; i++) { 391 | khstring_append(&string, U'0'); 392 | } 393 | 394 | khstring_concatenate(&string, &hex); 395 | khstring_delete(&hex); 396 | 397 | return string; 398 | } 399 | } 400 | } 401 | 402 | static inline khstring khstring_quote(khstring* string) { 403 | khstring quoted_string = khstring_new(U"\""); 404 | 405 | for (char32_t* chr = *string; chr < *string + khstring_size(string); chr++) { 406 | if (*chr == U'\'') { 407 | khstring_append("ed_string, U'\''); 408 | } 409 | else { 410 | khstring escaped = kh_escapeChar(*chr); 411 | khstring_concatenate("ed_string, &escaped); 412 | khstring_delete(&escaped); 413 | } 414 | } 415 | 416 | khstring_append("ed_string, U'\"'); 417 | return quoted_string; 418 | } 419 | 420 | 421 | #ifdef __cplusplus 422 | } 423 | #endif 424 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # This file is a part of the Kithare programming language source code. 2 | # The source code for Kithare programming language is distributed under the MIT 3 | # license. 4 | # Copyright (C) 2022 Kithare Organization 5 | # 6 | # This is a stub makefile kept for user convenience. Kithare relies on a python 7 | # script to build Kithare. This makefile merely calls the Python builder, so 8 | # Python needs to be installed for this makefile to work 9 | 10 | ifeq ($(OS),Windows_NT) 11 | PYTHON = py -3 12 | else 13 | PYTHON = python3 14 | endif 15 | 16 | make: 17 | ${PYTHON} build.py 18 | 19 | test: make 20 | ${PYTHON} build.py --make test 21 | 22 | clean: 23 | ${PYTHON} build.py --clean all 24 | 25 | debug: 26 | ${PYTHON} build.py --make debug 27 | 28 | installer: 29 | ${PYTHON} build.py --make installer 30 | -------------------------------------------------------------------------------- /misc/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avaxar/kithare/934bf822cb983216bdb0cb25dfddd065ff1a5944/misc/banner.png -------------------------------------------------------------------------------- /misc/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avaxar/kithare/934bf822cb983216bdb0cb25dfddd065ff1a5944/misc/icon.ico -------------------------------------------------------------------------------- /misc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avaxar/kithare/934bf822cb983216bdb0cb25dfddd065ff1a5944/misc/logo.png -------------------------------------------------------------------------------- /misc/logo.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of the Kithare programming language source code. 3 | The source code for Kithare programming language is distributed under the MIT license. 4 | Copyright (C) 2022 Kithare Organization 5 | 6 | misc/logo.py 7 | Python w/ pygame script to render the Kithare logo. 8 | """ 9 | 10 | import pygame 11 | 12 | pygame.init() 13 | 14 | WIDTH, HEIGHT = 946, 946 15 | logo = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) 16 | banner = pygame.Surface((WIDTH * 4, HEIGHT), pygame.SRCALPHA) 17 | FACTOR = 1 / 16.0 18 | 19 | BLUE1 = (2, 5, 190) 20 | BLUE2 = (20, 60, 220) 21 | BLUE3 = (30, 110, 255) 22 | BLUE4 = (45, 150, 255) 23 | 24 | ORANGE1 = (240, 120, 0) 25 | ORANGE2 = (255, 140, 0) 26 | 27 | 28 | def translate(c): 29 | return round(c[0] * FACTOR * (WIDTH - 1)), round(c[1] * FACTOR * (HEIGHT - 1)) 30 | 31 | 32 | def poly(points, color): 33 | pygame.draw.polygon(logo, color, list(map(translate, points))) 34 | pygame.draw.polygon(banner, color, list(map(translate, points))) 35 | 36 | 37 | # A1 piece 38 | poly([(2, 7), (5, 7), (5, 5), (2, 6)], BLUE2) 39 | 40 | # A2 piece 41 | poly([(2, 8), (5, 8), (5, 10), (2, 9)], BLUE2) 42 | 43 | # B1 piece 44 | poly([(2, 5), (5, 4), (5, 1), (2, 1)], ORANGE1) 45 | 46 | # B2 piece 47 | poly([(2, 10), (5, 11), (5, 14), (2, 14)], ORANGE1) 48 | 49 | # C1 piece 50 | poly([(6, 7), (9, 7), (13, 3), (9, 4)], BLUE3) 51 | 52 | # C2 piece 53 | poly([(6, 8), (9, 8), (13, 12), (9, 11)], BLUE3) 54 | 55 | # D1 piece 56 | poly([(10, 3), (14, 2), (15, 1), (12, 1)], ORANGE2) 57 | 58 | # D2 piece 59 | poly([(10, 12), (14, 13), (15, 14), (12, 14)], ORANGE2) 60 | 61 | # T1 piece 62 | poly( 63 | [ 64 | (0, 7.5), 65 | (1, 6), 66 | (1, 9), 67 | ], 68 | BLUE1, 69 | ) 70 | 71 | # T2 piece 72 | poly( 73 | [ 74 | (10, 7.5), 75 | (15, 3), 76 | (15, 12), 77 | ], 78 | BLUE4, 79 | ) 80 | 81 | pygame.image.save(logo, "logo.png") 82 | pygame.image.save(banner, "banner.png") 83 | -------------------------------------------------------------------------------- /misc/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avaxar/kithare/934bf822cb983216bdb0cb25dfddd065ff1a5944/misc/small.png -------------------------------------------------------------------------------- /src/cli.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef _WIN32 15 | #include 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | 30 | static int argi = 1; 31 | static kharray(khstring) args = NULL; 32 | 33 | 34 | static int help(void) { 35 | puts(kh_ANSI_BOLD "Kithare programming language Compiler and Runtime (kcr) " kh_VERSION_STR); 36 | puts(kh_ANSI_RESET "Copyright (C) 2022 Kithare Organization at " kh_ANSI_FG_CYAN kh_ANSI_UNDERLINE 37 | "https://www.kithare.de" kh_ANSI_RESET "; distributed under the " 38 | "MIT license.\n"); 39 | 40 | puts(" " kh_ANSI_BOLD "kcr [help]" kh_ANSI_RESET " : shows this help page."); 41 | puts(" " kh_ANSI_BOLD "kcr version" kh_ANSI_RESET " : shows the used Kithare version."); 42 | puts(" " kh_ANSI_BOLD "kcr [run] [... arguments]" kh_ANSI_RESET 43 | " : builds and runs source file."); 44 | puts(" " kh_ANSI_BOLD "kcr debug [... arguments]" kh_ANSI_RESET 45 | " : builds and runs source file on debug mode for debugging."); 46 | puts(" " kh_ANSI_BOLD "kcr build [executable.exe]" kh_ANSI_RESET 47 | " : builds source file."); 48 | puts(" " kh_ANSI_BOLD "kcr lexicate " kh_ANSI_RESET 49 | " : lexicates source file into tokens."); 50 | puts(" " kh_ANSI_BOLD "kcr parse " kh_ANSI_RESET 51 | " : parses source file into an AST tree."); 52 | puts(" " kh_ANSI_BOLD "kcr semantic " kh_ANSI_RESET 53 | " : semantically analyze source file into a semantic graph."); 54 | 55 | puts("\n`" kh_ANSI_BOLD "[...]" kh_ANSI_RESET "` arguments are optional. `" kh_ANSI_BOLD 56 | "<...>" kh_ANSI_RESET "` arguments are required input arguments."); 57 | 58 | return 0; 59 | } 60 | 61 | static int version(void) { 62 | puts(kh_VERSION_STR); 63 | return 0; 64 | } 65 | 66 | static int run(void) { 67 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "unimplemented command: " kh_ANSI_RESET "run\n", stderr); 68 | return 1; 69 | } 70 | 71 | static int debug(void) { 72 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "unimplemented command: " kh_ANSI_RESET "debug\n", stderr); 73 | return 1; 74 | } 75 | 76 | static int build(void) { 77 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "unimplemented command: " kh_ANSI_RESET "build\n", stderr); 78 | return 1; 79 | } 80 | 81 | static int lexicate(void) { 82 | if (argi >= kharray_size(&args)) { 83 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "missing required argument: " kh_ANSI_RESET "file\n", stderr); 84 | return 1; 85 | } 86 | else if (kharray_size(&args) > 3) { 87 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "too many arguments; " kh_ANSI_RESET kh_ANSI_BOLD 88 | "lexicate" kh_ANSI_RESET 89 | " only requires exactly 1 argument.\n", 90 | stderr); 91 | return 1; 92 | } 93 | 94 | // Read file provided by argument 95 | bool file_exists; 96 | khbuffer content_buf = kh_readFile(&args[argi], &file_exists); 97 | khstring content = kh_decodeUtf8(&content_buf); 98 | khbuffer_delete(&content_buf); 99 | argi++; 100 | 101 | if (!file_exists) { 102 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "file not found: " kh_ANSI_RESET, stderr); 103 | kh_putln(&args[argi - 1], stderr); 104 | 105 | khstring_delete(&content); 106 | return 1; 107 | } 108 | 109 | puts("{"); 110 | puts("\"tokens\": ["); 111 | 112 | // Print tokens 113 | kharray(khToken) tokens = kh_lexicate(&content); 114 | for (size_t i = 0; i < kharray_size(&tokens); i++) { 115 | khstring token_str = khToken_string(&(tokens[i]), content); 116 | kh_put(&token_str, stdout); 117 | khstring_delete(&token_str); 118 | 119 | if (i < kharray_size(&tokens) - 1) { 120 | printf(",\n"); 121 | } 122 | else { 123 | printf("\n"); 124 | } 125 | } 126 | 127 | puts("],"); 128 | puts("\"errors\": ["); 129 | 130 | // Print errors 131 | size_t errors = kh_hasErrors(); 132 | for (size_t i = 0; i < errors; i++) { 133 | khError* error = &(*kh_getErrors())[i]; 134 | khstring message = khstring_quote(&error->message); 135 | 136 | printf("{\"index\": %lu, \"message\": ", (unsigned long)((char32_t*)error->data - content)); 137 | kh_put(&message, stdout); 138 | printf("}"); 139 | 140 | khstring_delete(&message); 141 | 142 | if (i < errors - 1) { 143 | printf(",\n"); 144 | } 145 | else { 146 | printf("\n"); 147 | } 148 | } 149 | 150 | kh_flushErrors(); 151 | puts("]"); 152 | puts("}"); 153 | 154 | khstring_delete(&content); 155 | kharray_delete(&tokens); 156 | 157 | return errors; 158 | } 159 | 160 | static int parse(void) { 161 | if (argi >= kharray_size(&args)) { 162 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "missing required argument: " kh_ANSI_RESET "file\n", stderr); 163 | return 1; 164 | } 165 | else if (kharray_size(&args) > 3) { 166 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "too many arguments; " kh_ANSI_RESET kh_ANSI_BOLD 167 | "parse" kh_ANSI_RESET " only requires exactly 1 argument.\n", 168 | stderr); 169 | return 1; 170 | } 171 | 172 | // Read file provided by argument 173 | bool file_exists; 174 | khbuffer content_buf = kh_readFile(&args[argi], &file_exists); 175 | khstring content = kh_decodeUtf8(&content_buf); 176 | khbuffer_delete(&content_buf); 177 | argi++; 178 | 179 | if (!file_exists) { 180 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "file not found: " kh_ANSI_RESET, stderr); 181 | kh_putln(&args[argi - 1], stderr); 182 | 183 | khstring_delete(&content); 184 | return 1; 185 | } 186 | 187 | puts("{"); 188 | puts("\"ast\": ["); 189 | 190 | // Print statements 191 | kharray(khAstStatement) ast = kh_parse(&content); 192 | for (size_t i = 0; i < kharray_size(&ast); i++) { 193 | khstring statement_str = khAstStatement_string(&ast[i], content); 194 | kh_put(&statement_str, stdout); 195 | khstring_delete(&statement_str); 196 | 197 | if (i < kharray_size(&ast) - 1) { 198 | printf(",\n"); 199 | } 200 | else { 201 | printf("\n"); 202 | } 203 | } 204 | 205 | puts("],"); 206 | puts("\"errors\": ["); 207 | 208 | // Print errors 209 | size_t errors = kh_hasErrors(); 210 | for (size_t i = 0; i < errors; i++) { 211 | khError* error = &(*kh_getErrors())[i]; 212 | khstring message = khstring_quote(&error->message); 213 | 214 | printf("{\"index\": %lu, \"message\": ", (unsigned long)((char32_t*)error->data - content)); 215 | kh_put(&message, stdout); 216 | printf("}"); 217 | 218 | khstring_delete(&message); 219 | 220 | if (i < errors - 1) { 221 | printf(",\n"); 222 | } 223 | else { 224 | printf("\n"); 225 | } 226 | } 227 | 228 | kh_flushErrors(); 229 | puts("]"); 230 | puts("}"); 231 | 232 | khstring_delete(&content); 233 | kharray_delete(&ast); 234 | 235 | return errors; 236 | } 237 | 238 | static int semantic(void) { 239 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "unimplemented command: " kh_ANSI_RESET "semantic\n", stderr); 240 | return 1; 241 | } 242 | 243 | 244 | // Entry point of the Kithare CLI program 245 | #ifdef _WIN32 246 | int wmain(int argc, wchar_t* argv[]) 247 | #else 248 | int main(int argc, char* argv[]) 249 | #endif 250 | { 251 | setlocale(LC_ALL, "en_US.utf8"); 252 | setlocale(LC_TIME, "en_GB.utf8"); 253 | 254 | #ifdef _WIN32 255 | SetConsoleOutputCP(CP_UTF8); 256 | setvbuf(stdout, NULL, _IONBF, 0); 257 | setvbuf(stdin, NULL, _IOFBF, 256); 258 | system(" "); 259 | #endif 260 | 261 | args = kharray_new(khstring, khstring_delete); 262 | 263 | kharray_reserve(&args, argc); 264 | for (int i = 0; i < argc; i++) { 265 | #ifdef _WIN32 266 | kharray_append(&args, khstring_new(U"")); 267 | 268 | size_t length = wcslen(argv[i]); 269 | kharray_reserve(&args[i], length); 270 | for (wchar_t* wchr = argv[i]; wchr < argv[i] + length; wchr++) { 271 | kharray_append(&args[i], *wchr); 272 | } 273 | #else 274 | khbuffer arg = khbuffer_new(argv[i]); 275 | kharray_append(&args, kh_decodeUtf8(&arg)); 276 | khbuffer_delete(&arg); 277 | #endif 278 | } 279 | 280 | int code = 0; 281 | 282 | if (kharray_size(&args) == 1 || khstring_equalCstring(&args[1], U"help")) { 283 | argi++; 284 | code = help(); 285 | } 286 | else if (khstring_equalCstring(&args[1], U"version")) { 287 | argi++; 288 | code = version(); 289 | } 290 | else if (khstring_equalCstring(&args[1], U"run")) { 291 | argi++; 292 | code = run(); 293 | } 294 | else if (khstring_equalCstring(&args[1], U"debug")) { 295 | argi++; 296 | code = debug(); 297 | } 298 | else if (khstring_equalCstring(&args[1], U"build")) { 299 | argi++; 300 | code = build(); 301 | } 302 | else if (khstring_equalCstring(&args[1], U"lexicate")) { 303 | argi++; 304 | code = lexicate(); 305 | } 306 | else if (khstring_equalCstring(&args[1], U"parse")) { 307 | argi++; 308 | code = parse(); 309 | } 310 | else if (khstring_equalCstring(&args[1], U"semantic")) { 311 | argi++; 312 | code = semantic(); 313 | } 314 | else if (khstring_endsWithCstring(&args[1], U".kh")) { 315 | code = run(); 316 | } 317 | else { 318 | fputs(kh_ANSI_BOLD kh_ANSI_FG_RED "unknown command: " kh_ANSI_RESET, stderr); 319 | kh_putln(&args[argi], stderr); 320 | fputs(kh_ANSI_FG_YELLOW "Use `" kh_ANSI_RESET kh_ANSI_BOLD 321 | "kcr help" kh_ANSI_RESET kh_ANSI_FG_YELLOW 322 | "` for the available command arguments.\n" kh_ANSI_RESET, 323 | stderr); 324 | code = 1; 325 | } 326 | 327 | kharray_delete(&args); 328 | return code; 329 | } 330 | -------------------------------------------------------------------------------- /src/error.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | 14 | static _Thread_local kharray(khError) error_stack = NULL; 15 | 16 | 17 | void kh_raiseError(khError error) { 18 | if (error_stack == NULL) { 19 | error_stack = kharray_new(khError, khError_delete); 20 | } 21 | 22 | bool is_duplicate = false; 23 | for (size_t i = 0; i < kharray_size(&error_stack); i++) { 24 | khError* err = &error_stack[i]; 25 | 26 | if (err->type == error.type && khstring_equal(&err->message, &error.message) && 27 | err->data == error.data) { 28 | is_duplicate = true; 29 | break; 30 | } 31 | } 32 | 33 | if (is_duplicate) { 34 | khError_delete(&error); 35 | } 36 | else { 37 | kharray_append(&error_stack, error); 38 | } 39 | } 40 | 41 | size_t kh_hasErrors(void) { 42 | if (error_stack == NULL) { 43 | return 0; 44 | } 45 | else { 46 | return kharray_size(&error_stack); 47 | } 48 | } 49 | 50 | kharray(khError) * kh_getErrors(void) { 51 | if (error_stack == NULL) { 52 | error_stack = kharray_new(khError, khError_delete); 53 | } 54 | 55 | return &error_stack; 56 | } 57 | 58 | void kh_flushErrors(void) { 59 | if (error_stack != NULL) { 60 | kharray_delete(&error_stack); 61 | error_stack = NULL; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/mingw.rc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/kithare/core/info.h" 3 | 4 | 0 ICON "../misc/icon.ico" 5 | 6 | 1 VERSIONINFO 7 | FILEVERSION kh_VERSION_MAJOR, kh_VERSION_MINOR, kh_VERSION_PATCH, 0 8 | PRODUCTVERSION kh_VERSION_MAJOR, kh_VERSION_MINOR, kh_VERSION_PATCH, 0 9 | FILEFLAGSMASK 0x3fL 10 | FILEFLAGS 0x0L 11 | FILEOS VOS__WINDOWS32 12 | FILETYPE VFT_APP 13 | FILESUBTYPE 0x0L 14 | BEGIN 15 | BLOCK "StringFileInfo" 16 | BEGIN 17 | BLOCK "040904E4" 18 | BEGIN 19 | VALUE "CompanyName", "Kithare\0" 20 | VALUE "FileDescription", "The Kithare programming language compiler\0" 21 | VALUE "FileVersion", kh_VERSION_STR "\0" 22 | VALUE "InternalName", "kcr\0" 23 | VALUE "LegalCopyright", "Copyright \xA9 Kithare Organization\0" 24 | VALUE "OriginalFilename", "kcr.exe\0" 25 | VALUE "ProductName", "Kithare\0" 26 | VALUE "ProductVersion", kh_VERSION_STR "\0" 27 | END 28 | END 29 | BLOCK "VarFileInfo" 30 | BEGIN 31 | VALUE "Translation", 0x409, 1252 32 | END 33 | END 34 | -------------------------------------------------------------------------------- /src/token.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a part of the Kithare programming language source code. 3 | * The source code for Kithare programming language is distributed under the MIT license, 4 | * and it is available as a repository at https://github.com/avaxar/Kithare 5 | * Copyright (C) 2022 Kithare Organization 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | 12 | khstring khTokenType_string(khTokenType type) { 13 | switch (type) { 14 | case khTokenType_INVALID: 15 | return khstring_new(U"invalid"); 16 | case khTokenType_EOF: 17 | return khstring_new(U"eof"); 18 | case khTokenType_NEWLINE: 19 | return khstring_new(U"newline"); 20 | case khTokenType_COMMENT: 21 | return khstring_new(U"comment"); 22 | 23 | case khTokenType_IDENTIFIER: 24 | return khstring_new(U"identifier"); 25 | case khTokenType_KEYWORD: 26 | return khstring_new(U"keyword"); 27 | case khTokenType_DELIMITER: 28 | return khstring_new(U"delimiter"); 29 | case khTokenType_OPERATOR: 30 | return khstring_new(U"operator"); 31 | 32 | case khTokenType_CHAR: 33 | return khstring_new(U"char"); 34 | case khTokenType_STRING: 35 | return khstring_new(U"string"); 36 | case khTokenType_BUFFER: 37 | return khstring_new(U"buffer"); 38 | 39 | case khTokenType_BYTE: 40 | return khstring_new(U"byte"); 41 | case khTokenType_INTEGER: 42 | return khstring_new(U"integer"); 43 | case khTokenType_UINTEGER: 44 | return khstring_new(U"uinteger"); 45 | case khTokenType_FLOAT: 46 | return khstring_new(U"float"); 47 | case khTokenType_DOUBLE: 48 | return khstring_new(U"double"); 49 | case khTokenType_IDOUBLE: 50 | return khstring_new(U"idouble"); 51 | case khTokenType_IFLOAT: 52 | return khstring_new(U"ifloat"); 53 | 54 | default: 55 | return khstring_new(U"unknown"); 56 | } 57 | } 58 | 59 | 60 | khstring khKeywordToken_string(khKeywordToken keyword) { 61 | switch (keyword) { 62 | case khKeywordToken_IMPORT: 63 | return khstring_new(U"import"); 64 | case khKeywordToken_INCLUDE: 65 | return khstring_new(U"include"); 66 | case khKeywordToken_AS: 67 | return khstring_new(U"as"); 68 | case khKeywordToken_DEF: 69 | return khstring_new(U"def"); 70 | case khKeywordToken_CLASS: 71 | return khstring_new(U"class"); 72 | case khKeywordToken_INHERITS: 73 | return khstring_new(U"inherits"); 74 | case khKeywordToken_STRUCT: 75 | return khstring_new(U"struct"); 76 | case khKeywordToken_ENUM: 77 | return khstring_new(U"enum"); 78 | case khKeywordToken_ALIAS: 79 | return khstring_new(U"alias"); 80 | 81 | case khKeywordToken_REF: 82 | return khstring_new(U"ref"); 83 | case khKeywordToken_WILD: 84 | return khstring_new(U"wild"); 85 | case khKeywordToken_INCASE: 86 | return khstring_new(U"incase"); 87 | case khKeywordToken_STATIC: 88 | return khstring_new(U"static"); 89 | 90 | case khKeywordToken_IF: 91 | return khstring_new(U"if"); 92 | case khKeywordToken_ELIF: 93 | return khstring_new(U"elif"); 94 | case khKeywordToken_ELSE: 95 | return khstring_new(U"else"); 96 | case khKeywordToken_FOR: 97 | return khstring_new(U"for"); 98 | case khKeywordToken_IN: 99 | return khstring_new(U"in"); 100 | case khKeywordToken_WHILE: 101 | return khstring_new(U"while"); 102 | case khKeywordToken_DO: 103 | return khstring_new(U"do"); 104 | case khKeywordToken_BREAK: 105 | return khstring_new(U"break"); 106 | case khKeywordToken_CONTINUE: 107 | return khstring_new(U"continue"); 108 | case khKeywordToken_RETURN: 109 | return khstring_new(U"return"); 110 | 111 | default: 112 | return khstring_new(U"unknown"); 113 | } 114 | } 115 | 116 | 117 | khstring khDelimiterToken_string(khDelimiterToken delimiter) { 118 | switch (delimiter) { 119 | case khDelimiterToken_DOT: 120 | return khstring_new(U"."); 121 | case khDelimiterToken_COMMA: 122 | return khstring_new(U","); 123 | case khDelimiterToken_COLON: 124 | return khstring_new(U":"); 125 | case khDelimiterToken_SEMICOLON: 126 | return khstring_new(U";"); 127 | case khDelimiterToken_EXCLAMATION: 128 | return khstring_new(U"!"); 129 | 130 | case khDelimiterToken_PARENTHESIS_OPEN: 131 | return khstring_new(U"("); 132 | case khDelimiterToken_PARENTHESIS_CLOSE: 133 | return khstring_new(U")"); 134 | case khDelimiterToken_CURLY_BRACKET_OPEN: 135 | return khstring_new(U"{"); 136 | case khDelimiterToken_CURLY_BRACKET_CLOSE: 137 | return khstring_new(U"}"); 138 | case khDelimiterToken_SQUARE_BRACKET_OPEN: 139 | return khstring_new(U"["); 140 | case khDelimiterToken_SQUARE_BRACKET_CLOSE: 141 | return khstring_new(U"]"); 142 | 143 | case khDelimiterToken_ARROW: 144 | return khstring_new(U"->"); 145 | case khDelimiterToken_ELLIPSIS: 146 | return khstring_new(U"..."); 147 | 148 | default: 149 | return khstring_new(U"unknown"); 150 | } 151 | } 152 | 153 | 154 | khstring khOperatorToken_string(khOperatorToken operator_v) { 155 | switch (operator_v) { 156 | case khOperatorToken_ASSIGN: 157 | return khstring_new(U"="); 158 | case khOperatorToken_RANGE: 159 | return khstring_new(U".."); 160 | 161 | case khOperatorToken_ADD: 162 | return khstring_new(U"+"); 163 | case khOperatorToken_SUB: 164 | return khstring_new(U"-"); 165 | case khOperatorToken_MUL: 166 | return khstring_new(U"*"); 167 | case khOperatorToken_DIV: 168 | return khstring_new(U"/"); 169 | case khOperatorToken_MOD: 170 | return khstring_new(U"%"); 171 | case khOperatorToken_DOT: 172 | return khstring_new(U"@"); 173 | case khOperatorToken_POW: 174 | return khstring_new(U"^"); 175 | 176 | case khOperatorToken_IP_ADD: 177 | return khstring_new(U"+="); 178 | case khOperatorToken_IP_SUB: 179 | return khstring_new(U"-="); 180 | case khOperatorToken_IP_MUL: 181 | return khstring_new(U"*="); 182 | case khOperatorToken_IP_DIV: 183 | return khstring_new(U"/="); 184 | case khOperatorToken_IP_MOD: 185 | return khstring_new(U"%="); 186 | case khOperatorToken_IP_DOT: 187 | return khstring_new(U"@="); 188 | case khOperatorToken_IP_POW: 189 | return khstring_new(U"^="); 190 | 191 | case khOperatorToken_EQUAL: 192 | return khstring_new(U"=="); 193 | case khOperatorToken_UNEQUAL: 194 | return khstring_new(U"!="); 195 | case khOperatorToken_LESS: 196 | return khstring_new(U"<"); 197 | case khOperatorToken_GREATER: 198 | return khstring_new(U">"); 199 | case khOperatorToken_LESS_EQUAL: 200 | return khstring_new(U"<="); 201 | case khOperatorToken_GREATER_EQUAL: 202 | return khstring_new(U">="); 203 | 204 | case khOperatorToken_NOT: 205 | return khstring_new(U"not"); 206 | case khOperatorToken_AND: 207 | return khstring_new(U"and"); 208 | case khOperatorToken_OR: 209 | return khstring_new(U"or"); 210 | case khOperatorToken_XOR: 211 | return khstring_new(U"xor"); 212 | 213 | case khOperatorToken_BIT_NOT: 214 | return khstring_new(U"~"); 215 | case khOperatorToken_BIT_AND: 216 | return khstring_new(U"&"); 217 | case khOperatorToken_BIT_OR: 218 | return khstring_new(U"|"); 219 | case khOperatorToken_BIT_LSHIFT: 220 | return khstring_new(U"<<"); 221 | case khOperatorToken_BIT_RSHIFT: 222 | return khstring_new(U">>"); 223 | 224 | case khOperatorToken_IP_BIT_AND: 225 | return khstring_new(U"&="); 226 | case khOperatorToken_IP_BIT_OR: 227 | return khstring_new(U"|="); 228 | case khOperatorToken_IP_BIT_XOR: 229 | return khstring_new(U"~="); 230 | case khOperatorToken_IP_BIT_LSHIFT: 231 | return khstring_new(U"<<="); 232 | case khOperatorToken_IP_BIT_RSHIFT: 233 | return khstring_new(U">>="); 234 | 235 | default: 236 | return khstring_new(U"unknown"); 237 | } 238 | } 239 | 240 | 241 | khToken khToken_copy(khToken* token) { 242 | khToken copy = *token; 243 | 244 | switch (token->type) { 245 | case khTokenType_IDENTIFIER: 246 | copy.identifier = khstring_copy(&token->identifier); 247 | break; 248 | 249 | case khTokenType_STRING: 250 | copy.string = khstring_copy(&token->string); 251 | break; 252 | 253 | case khTokenType_BUFFER: 254 | copy.buffer = khbuffer_copy(&token->buffer); 255 | break; 256 | 257 | default: 258 | break; 259 | } 260 | 261 | return copy; 262 | } 263 | 264 | void khToken_delete(khToken* token) { 265 | switch (token->type) { 266 | case khTokenType_IDENTIFIER: 267 | khstring_delete(&token->identifier); 268 | break; 269 | 270 | case khTokenType_STRING: 271 | khstring_delete(&token->string); 272 | break; 273 | 274 | case khTokenType_BUFFER: 275 | khbuffer_delete(&token->buffer); 276 | break; 277 | 278 | default: 279 | break; 280 | } 281 | } 282 | 283 | khstring khToken_string(khToken* token, char32_t* origin) { 284 | khstring string = khstring_new(U"{\"type\": "); 285 | 286 | khstring type_str = khTokenType_string(token->type); 287 | khstring quoted_type_str = khstring_quote(&type_str); 288 | khstring_concatenate(&string, "ed_type_str); 289 | 290 | khstring_concatenateCstring(&string, U", \"begin\": "); 291 | if (token->begin != NULL) { 292 | khstring begin_str = kh_uintToString(token->begin - origin, 10); 293 | khstring_concatenate(&string, &begin_str); 294 | khstring_delete(&begin_str); 295 | } 296 | else { 297 | khstring_concatenateCstring(&string, U"null"); 298 | } 299 | 300 | khstring_concatenateCstring(&string, U", \"end\": "); 301 | if (token->end != NULL) { 302 | khstring end_str = kh_uintToString(token->end - origin, 10); 303 | khstring_concatenate(&string, &end_str); 304 | khstring_delete(&end_str); 305 | } 306 | else { 307 | khstring_concatenateCstring(&string, U"null"); 308 | } 309 | 310 | khstring_concatenateCstring(&string, U", \"value\": "); 311 | khstring value; 312 | switch (token->type) { 313 | case khTokenType_IDENTIFIER: 314 | value = khstring_quote(&token->identifier); 315 | break; 316 | case khTokenType_KEYWORD: { 317 | khstring keyword_str = khKeywordToken_string(token->keyword); 318 | value = khstring_quote(&keyword_str); 319 | khstring_delete(&keyword_str); 320 | } break; 321 | case khTokenType_DELIMITER: { 322 | khstring delimiter_str = khDelimiterToken_string(token->delimiter); 323 | value = khstring_quote(&delimiter_str); 324 | khstring_delete(&delimiter_str); 325 | } break; 326 | case khTokenType_OPERATOR: { 327 | khstring operator_str = khOperatorToken_string(token->operator_v); 328 | value = khstring_quote(&operator_str); 329 | khstring_delete(&operator_str); 330 | } break; 331 | 332 | case khTokenType_CHAR: 333 | khstring_append(&string, U'\"'); 334 | value = kh_escapeChar(token->char_v); 335 | khstring_append(&value, U'\"'); 336 | break; 337 | case khTokenType_STRING: 338 | value = khstring_quote(&token->string); 339 | break; 340 | case khTokenType_BUFFER: 341 | value = khbuffer_quote(&token->buffer); 342 | break; 343 | 344 | case khTokenType_BYTE: 345 | khstring_append(&string, U'\"'); 346 | value = kh_escapeChar(token->byte); 347 | khstring_append(&value, U'\"'); 348 | break; 349 | case khTokenType_INTEGER: 350 | value = kh_intToString(token->integer, 10); 351 | break; 352 | case khTokenType_UINTEGER: 353 | value = kh_uintToString(token->uinteger, 10); 354 | break; 355 | case khTokenType_FLOAT: 356 | value = kh_floatToString(token->float_v, 8, 10); 357 | break; 358 | case khTokenType_DOUBLE: 359 | value = kh_floatToString(token->double_v, 16, 10); 360 | break; 361 | case khTokenType_IFLOAT: 362 | value = kh_floatToString(token->ifloat, 8, 10); 363 | break; 364 | case khTokenType_IDOUBLE: 365 | value = kh_floatToString(token->idouble, 16, 10); 366 | break; 367 | 368 | default: 369 | value = khstring_new(U"null"); 370 | break; 371 | } 372 | 373 | khstring_concatenate(&string, &value); 374 | khstring_delete(&value); 375 | khstring_append(&string, U'}'); 376 | 377 | return string; 378 | } 379 | --------------------------------------------------------------------------------