├── .circleci └── config.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── code ├── include │ ├── common │ │ ├── bytecode.hpp │ │ ├── debug.hpp │ │ ├── error.hpp │ │ ├── token.hpp │ │ ├── type.hpp │ │ └── util.hpp │ ├── interpreter │ │ ├── interpreter.hpp │ │ └── interpreter_scope.hpp │ ├── language.hpp │ ├── lexer │ │ └── lexer.hpp │ ├── parse_args.hpp │ └── parser │ │ ├── ast │ │ ├── expression.hpp │ │ ├── expression_operator.hpp │ │ └── statement.hpp │ │ ├── code_gen.hpp │ │ ├── expression_parser.hpp │ │ ├── statement_parser.hpp │ │ └── statement_scope.hpp ├── src │ ├── common │ │ ├── bytecode.cpp │ │ ├── error.cpp │ │ ├── token.cpp │ │ └── type.cpp │ ├── interpreter │ │ ├── interpreter.cpp │ │ └── interpreter_scope.cpp │ ├── language.cpp │ ├── lexer │ │ └── lexer.cpp │ ├── main.cpp │ ├── parse_args.cpp │ └── parser │ │ ├── ast │ │ ├── expression.cpp │ │ ├── expression_operator.cpp │ │ └── statement.cpp │ │ ├── code_gen.cpp │ │ ├── expression_parser.cpp │ │ ├── statement_parser.cpp │ │ └── statement_scope.cpp └── version │ ├── version.hpp │ └── version.hpp.in ├── samples ├── programs │ ├── ccc │ │ └── 2025_j3.night │ ├── cs50 │ │ ├── w1_cash.night │ │ ├── w1_credit.night │ │ ├── w1_mario.night │ │ ├── w2_caesar.night │ │ ├── w2_readability.night │ │ ├── w2_scrabble.night │ │ └── w2_substitution.night │ ├── leetcode │ │ ├── 1_twosum.night │ │ ├── 20_parantheses.night │ │ ├── 21_mergelists.night │ │ ├── 26_removeduplicates.night │ │ └── 9_palindrome.night │ ├── math │ │ ├── collatz_conjecture.night │ │ └── fizzbuzz.night │ └── other │ │ └── turingmachine.night ├── stdio │ ├── ccc │ │ ├── 2025_j3_expected.txt │ │ └── 2025_j3_input.txt │ ├── cs50 │ │ ├── w1_cash_expected.txt │ │ ├── w1_cash_input.txt │ │ ├── w1_credit_expected.txt │ │ ├── w1_credit_input.txt │ │ ├── w1_mario_expected.txt │ │ ├── w1_mario_input.txt │ │ ├── w2_caesar_expected.txt │ │ ├── w2_caesar_input.txt │ │ ├── w2_readability_expected.txt │ │ ├── w2_readability_input.txt │ │ ├── w2_scrabble_expected.txt │ │ ├── w2_scrabble_input.txt │ │ ├── w2_substitution_expected.txt │ │ └── w2_substitution_input.txt │ ├── leetcode │ │ ├── 1_twosum_expected.txt │ │ ├── 1_twosum_input.txt │ │ ├── 20_parantheses_expected.txt │ │ ├── 20_parantheses_input.txt │ │ ├── 21_mergelists_expected.txt │ │ ├── 21_mergelists_input.txt │ │ ├── 26_removeduplicates_expected.txt │ │ ├── 26_removeduplicates_input.txt │ │ ├── 9_palindrome_expected.txt │ │ └── 9_palindrome_input.txt │ └── math │ │ ├── collatz_conjecture_expected.txt │ │ ├── collatz_conjecture_input.txt │ │ ├── fizzbuzz_expected.txt │ │ └── fizzbuzz_input.txt └── test_samples.sh └── tests ├── include ├── code_generation_tests.hpp ├── expression_parser_units.hpp ├── ntest.hpp └── predefined_functions.hpp ├── programs ├── arrays.night ├── basic.night └── functions.night ├── src ├── ntest.cpp └── test.cpp ├── stdio ├── arrays_expected.txt ├── arrays_input.txt ├── basic_expected.txt ├── basic_input.txt ├── functions_expected.txt └── functions_input.txt └── test_night.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | test: 5 | docker: 6 | - image: cimg/base:edge-22.04 7 | steps: 8 | - checkout 9 | # Install CMake 10 | - run: 11 | name: Install CMake 12 | command: | 13 | sudo apt-get update 14 | sudo apt-get install -y cmake 15 | # Create build directory and build the project 16 | - run: 17 | name: Build the project 18 | command: | 19 | mkdir -p build 20 | cd build 21 | cmake .. 22 | cmake --build . --config Release 23 | # Run unit tests 24 | - run: 25 | name: Running unit tests 26 | command: | 27 | build/night-tests 28 | # Run integration tests 29 | - run: 30 | name: Running integration tests 31 | command: | 32 | cd tests 33 | bash test_night.sh ../build/night 34 | echo 35 | cd ../samples 36 | bash test_samples.sh ../build/night 37 | 38 | workflows: 39 | version: 2 40 | tests: 41 | jobs: 42 | - test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # visual studio 2 | CMakeSettings.json 3 | /.vs 4 | /out 5 | 6 | # vscode 7 | /.vscode 8 | 9 | # code 10 | source.night 11 | tests/actual_output.txt 12 | 13 | # git 14 | /.git 15 | 16 | # cmake 17 | /build 18 | /winbuild 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | set(CMAKE_CXX_STANDARD 20) 3 | set(CMAKE_CXX_STANDARD_REQUIRED True) 4 | 5 | project(night VERSION 0.0.3) 6 | 7 | set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static") 8 | 9 | configure_file("${CMAKE_SOURCE_DIR}/code/version/version.hpp.in" 10 | "${CMAKE_SOURCE_DIR}/code/version/version.hpp") 11 | 12 | include_directories("${CMAKE_SOURCE_DIR}/code/version" 13 | "${CMAKE_SOURCE_DIR}/code/include") 14 | 15 | # Night 16 | 17 | file(GLOB_RECURSE SOURCE_FILES ${CMAKE_SOURCE_DIR} "code/src/*.cpp") 18 | add_executable(night ${SOURCE_FILES}) 19 | 20 | target_link_libraries(night PRIVATE -static) 21 | 22 | # Night Tests 23 | 24 | file(GLOB_RECURSE TEST_FILES ${CMAKE_SOURCE_DIR} "tests/src/*.cpp") 25 | list(FILTER SOURCE_FILES EXCLUDE REGEX ".*main\\.cpp$") 26 | add_executable(night-tests ${SOURCE_FILES} ${TEST_FILES}) 27 | 28 | target_include_directories(night-tests PRIVATE 29 | "${CMAKE_SOURCE_DIR}/code/include" 30 | "${CMAKE_SOURCE_DIR}/tests/include") 31 | 32 | target_link_libraries(night-tests -static) 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Night 2 | 3 | Night is a bytecode interpreted language. 4 | 5 | ## Table of contents 6 | 7 | - [Usage](#usage) 8 | - [Build](#build) 9 | - [How it Works](#how-it-works) 10 | 11 | --- 12 | 13 | ## Usage 14 | 15 | You can download either the Windows or Linux binary file in the [Releases section](https://github.com/alexapostolu/night/releases). 16 | 17 | Then create a `.night` file containing your Night code. Example programs can be found under the [`programs`](https://github.com/alexapostolu/night/tree/main/tests/programs) directory. 18 | 19 | To run a Night file, simply type, 20 | 21 | ``` 22 | night source.night 23 | ``` 24 | 25 | Try it out now with this simple Night code! 26 | 27 | ###### *source.night* 28 | ```py 29 | # recursive fib function 30 | def fib(n int) int 31 | { 32 | if (n == 0 || n == 1) 33 | return n; 34 | 35 | return fib(n - 1) + fib(n - 2); 36 | } 37 | 38 | print("Enter a number: "); 39 | n int = int(input()); 40 | 41 | for (i int = 0; i < n; i += 1) 42 | print(str(fib(i)) + " "); 43 | ``` 44 | 45 | The [`programs`](https://github.com/alexapostolu/night/tree/main/tests/programs) directory contain simple programs written in Night, including some Harvard's [CS50](https://cs50.harvard.edu/college/2023/spring/) programs and some common Math programs. These programs are also used in the CI pipeline as integration tests. 46 | 47 | --- 48 | 49 | ## Build 50 | 51 | Dependenies, `cmake` `g++` 52 | 53 | First, clone the repo and move into that directory. Generate MinGW or Unix Makefiles using `cmake`, and then build (optionally specify a Release build with `--Release`). You should then see a `night` exectuable file in the current directory. 54 | 55 | **Windows Build:** 56 | 57 | ```sh 58 | git clone https://github.com/alex/apostolu/night.git 59 | cd night 60 | 61 | mkdir build 62 | cd build 63 | 64 | cmake -G "MinGW Makefiles" .. 65 | cmake --build . 66 | 67 | night source.night 68 | ``` 69 | 70 | **Linux Build:** 71 | 72 | ```sh 73 | cd night 74 | 75 | mkdir build 76 | cd build 77 | 78 | cmake -G "Unix Makefiles" .. 79 | cmake --build . 80 | 81 | ./night source.night 82 | ``` 83 | -------------------------------------------------------------------------------- /code/include/common/bytecode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * @brief Data structures representing bytecodes and how they are stored. 10 | * 11 | * Bytecode type refers to an action bytecode, while bytecode value refers to the 12 | * value(s) the action is being performed on. For example, "ADD" is a bytecode 13 | * type and "2" and "3" are bytecode values. Both are represented as 8 bit values 14 | * and are stored in the same data structure. The only way to differentiate 15 | * between the two when interpreting is solely through the language design. 16 | * 17 | * **Design Decision #1** 18 | * bytecode_t is 8 bits. 19 | * 20 | * My bytecode only has 63 possible values right now, and even if I did expand 21 | * it, I would have to expand it by over twice to reach the limit of 8 bits. So 22 | * 8 bits is plenty enough for Night. 23 | * 24 | * **Design Decision #2** 25 | * bytecodes_t is a forward list and not a vector. 26 | * 27 | * The main operations are appending/inserting other bytecode containers, and 28 | * list has a better time complexity for that operation than vector. (I'm actually 29 | * not sure I think it's implementation specific, but from what I learned in my 30 | * data structures coding class is that list is better for these operations) 31 | */ 32 | using bytecode_t = uint8_t; 33 | using bytecodes_t = std::list; 34 | 35 | /** 36 | * @brief Enumeration of all bytecode types in Night 37 | * 38 | * The comments on some bytecode types represent how it is meant to be used, or 39 | * in other words, how it should appear on the Interpreter's stack. The comments 40 | * represent a stack growing from left to right. 41 | * 42 | * In the comments, the term "numeric" represents an expansion of an integer, 43 | * float or a numeric expression. For example "numeric" could represent 44 | * "S_INT2 uint8 uint8" or "S_INT1 uint8 S_INT1 uint8 ADD". 45 | * 46 | * **Design Decision #1** 47 | * BytecodeType is simply an "enum" and not an "enum class". 48 | * 49 | * Stroustrup did state three problems with enums, 50 | * https://www.stroustrup.com/C++11FAQ.html#enum 51 | * 1. Conventional enums implicitly convert to int, causing errors when 52 | * someone does not want an enumeration to act as an integer. 53 | * 2. Conventional enums export their enumerators to the surrounding scope, 54 | * causing name clashes. 55 | * 3. The underlying type of an enum cannot be specified, causing confusion, 56 | * compatibility problems, and makes forward declaration impossible. 57 | * 58 | * But these problems are irrelevant to "enum class BytecodeType", 59 | * 60 | * #1 is irrelevant because BytecodeType is supposed to be implicitly 61 | * converted to bytecode_t by design. #2 is solved because BytecodeType 62 | * prefixes all types with BytecodeType_. And #3 is solved because 63 | * BytecodeType specifies the underlying type as bytecode_t (C++11 feature). 64 | * 65 | * Now, not only does BytecodeType render the benefits of enum classes 66 | * unnecessary, but surpasses them by being shorter and looking oh so much nicer, 67 | * @code 68 | * codes.push_back((bytecode_t)BytecodeType::JUMP_IF_FALSE); // "enum class", wtf is this 69 | * codes.push_back(BytecodeType_JUMP_IF_FALSE); // "enum", much cleaner 70 | * @endcode 71 | * 72 | * **Design Decision #2** 73 | * There exists multiple integer sizes, not just INT8 and UINT8. 74 | * 75 | * In the interpreter, all ints are either stores as an 8 bit signed or unsigned 76 | * int, however, we still differentiate them here in bytecode to reduce the total 77 | * number of bytecodes present. 78 | */ 79 | enum : bytecode_t { 80 | _ByteType_INVALID_ = 0, 81 | 82 | ByteType_sINT1, 83 | ByteType_sINT2, 84 | ByteType_sINT4, 85 | ByteType_sINT8, 86 | ByteType_uINT1, 87 | ByteType_uINT2, 88 | ByteType_uINT4, 89 | ByteType_uINT8, 90 | 91 | ByteType_FLT4, 92 | ByteType_FLT8, 93 | 94 | BytecodeType_NEGATIVE_I, BytecodeType_NEGATIVE_F, // numeric, NEGATIVE 95 | BytecodeType_NOT_I, BytecodeType_NOT_F, // numeric, NOT 96 | 97 | BytecodeType_ADD_I, BytecodeType_ADD_F, BytecodeType_ADD_S, // numeric, numeric, ADD 98 | BytecodeType_SUB_I, BytecodeType_SUB_F, // numeric, numeric, SUB 99 | BytecodeType_MULT_I, BytecodeType_MULT_F, // numeric, numeric, MULT 100 | BytecodeType_DIV_I, BytecodeType_DIV_F, // numeric(non-zero), numeric, DIV 101 | ByteType_MOD, 102 | 103 | BytecodeType_LESSER_I, BytecodeType_LESSER_F, BytecodeType_LESSER_S, // numeric(left), numeric(right), LESSER 104 | BytecodeType_GREATER_I, BytecodeType_GREATER_F, BytecodeType_GREATER_S, // numeric(left), numeric(right), GREATER 105 | BytecodeType_LESSER_EQUALS_I, BytecodeType_LESSER_EQUALS_F, BytecodeType_LESSER_EQUALS_S, // numeric(left), numeric(right), LESSER_EQUALS 106 | BytecodeType_GREATER_EQUALS_I, BytecodeType_GREATER_EQUALS_F, BytecodeType_GREATER_EQUALS_S, // numeric(left), numeric(right), GREATER_EQUALS 107 | 108 | BytecodeType_EQUALS_I, BytecodeType_EQUALS_F, BytecodeType_EQUALS_S, // numeric(left), numeric(right), EQUALS 109 | BytecodeType_NOT_EQUALS_I, BytecodeType_NOT_EQUALS_F, BytecodeType_NOT_EQUALS_S, // numeric(left), numeric(right), NOT_EQUALS 110 | 111 | BytecodeType_AND, // numeric, numeric, AND 112 | BytecodeType_OR, // numeric, numeric, OR 113 | 114 | BytecodeType_INDEX_S, // numeric(string), INDEX_S 115 | BytecodeType_INDEX_A, // numeric(array), INDEX_A 116 | 117 | ByteType_LOAD, 118 | BytecodeType_LOAD_ELEM, 119 | 120 | ByteType_STORE, 121 | BytecodeType_STORE_INDEX_A, 122 | BytecodeType_STORE_INDEX_S, 123 | 124 | BytecodeType_ALLOCATE_STR, 125 | BytecodeType_ALLOCATE_ARR, 126 | BytecodeType_ALLOCATE_ARR_AND_FILL, 127 | BytecodeType_FREE_STR, 128 | BytecodeType_FREE_ARR, 129 | 130 | BytecodeType_JUMP, 131 | ByteType_JUMP_N, 132 | BytecodeType_JUMP_IF_FALSE, 133 | 134 | BytecodeType_RETURN, 135 | BytecodeType_CALL 136 | }; 137 | 138 | //template 139 | // requires std::is_same::value || 140 | // std::is_same::value 141 | //bytecodes_t int64_to_bytes(T i) 142 | //{ 143 | // bytecodes_t bytes; 144 | // 145 | // if (std::is_same::value) 146 | // bytes.push_back(ByteType_sINT8); 147 | // else 148 | // bytes.push_back(ByteType_uINT8); 149 | // 150 | // for (int j = 0; j < 8; ++j) 151 | // { 152 | // bytes.push_back(i & 0xFF); 153 | // i >>= 8; 154 | // } 155 | // 156 | // return bytes; 157 | //} 158 | 159 | template 160 | requires std::is_same::value || 161 | std::is_same::value || 162 | std::is_same::value || 163 | std::is_same::value || 164 | std::is_same::value || 165 | std::is_same::value || 166 | std::is_same::value || 167 | std::is_same::value 168 | bytecodes_t int_to_bytes(T i) 169 | { 170 | bytecodes_t bytes; 171 | 172 | if (std::is_same::value) 173 | bytes.push_back(ByteType_sINT1); 174 | else if (std::is_same::value) 175 | bytes.push_back(ByteType_uINT1); 176 | else if (std::is_same::value) 177 | bytes.push_back(ByteType_sINT2); 178 | else if (std::is_same::value) 179 | bytes.push_back(ByteType_uINT2); 180 | else if (std::is_same::value) 181 | bytes.push_back(ByteType_sINT4); 182 | else if (std::is_same::value) 183 | bytes.push_back(ByteType_uINT4); 184 | else if (std::is_same::value) 185 | bytes.push_back(ByteType_sINT8); 186 | else if (std::is_same::value) 187 | bytes.push_back(ByteType_uINT8); 188 | 189 | constexpr int byte_count = sizeof(T); 190 | 191 | for (int j = 0; j < byte_count; ++j) 192 | { 193 | bytes.push_back(i & 0xFF); 194 | i >>= 8; 195 | } 196 | 197 | return bytes; 198 | } 199 | 200 | namespace night { 201 | 202 | /** 203 | * @brief Bytecode type to string. Used in error messages and debugging. 204 | */ 205 | std::string to_str(bytecode_t type); 206 | 207 | } -------------------------------------------------------------------------------- /code/include/common/debug.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | concept Printable = requires(T t, std::stringstream ss) { ss << t; }; 9 | 10 | namespace debug 11 | { 12 | 13 | template 14 | [[nodiscard]] 15 | std::runtime_error unhandled_case(T type, std::source_location const& s_loc = std::source_location::current()) 16 | { 17 | std::stringstream s; 18 | s << " [Unhandled Case]\n" 19 | << " " << s_loc.file_name() << ':' << s_loc.line() << '\n' 20 | << " " << s_loc.function_name() << '\n' 21 | << " " << type << '\n'; 22 | 23 | return std::runtime_error(s.str()); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /code/include/common/error.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * There are three errors, minor, fatal and runtime. 3 | * 4 | * Minor errors do not impede the lexing, parsing, code generation or interpretation 5 | * of the program. The rest of the program should carry on with the normal task 6 | * after a minor error is created. An example would be a type error. 7 | * 8 | * Fatal errors do impede the future program. An example would be an unidentified 9 | * symbol. 10 | * 11 | * The Error functions take in source_location::current() as a default argument, 12 | * and if debug flag is set to true, then the source location of the line that 13 | * threw the error is displayed, allowing the programmer to easily find where 14 | * the error was thrown in the source code. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "token.hpp" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | enum class ErrorType 26 | { 27 | Warning, 28 | Minor, 29 | FatalCompile, 30 | FatalRuntime 31 | }; 32 | 33 | struct ErrorData 34 | { 35 | ErrorType type; 36 | Location location; 37 | std::source_location source_location; 38 | std::string message; 39 | 40 | Token token; 41 | }; 42 | 43 | namespace night { 44 | 45 | class error 46 | { 47 | public: 48 | static error& get(); 49 | 50 | bool has_minor_errors() const; 51 | 52 | void what(bool only_warnings = false); 53 | 54 | void create_warning( 55 | std::string const& msg, 56 | Location const& loc, 57 | std::source_location const& s_loc = std::source_location::current() 58 | ) noexcept; 59 | 60 | void create_minor_error( 61 | std::string const& msg, 62 | Location const& loc, 63 | std::source_location const& s_loc = std::source_location::current() 64 | ) noexcept; 65 | 66 | void create_minor_error( 67 | std::string const& message, 68 | Token const& token, 69 | std::source_location const& s_loc = std::source_location::current() 70 | ) noexcept; 71 | 72 | [[nodiscard]] 73 | error const& create_fatal_error( 74 | std::string const& msg, 75 | Location const& loc, 76 | std::source_location const& s_loc = std::source_location::current() 77 | ) noexcept; 78 | 79 | [[nodiscard]] 80 | error const& create_runtime_error( 81 | std::string const& msg, 82 | std::source_location const& s_loc = std::source_location::current() 83 | ) noexcept; 84 | 85 | public: 86 | void operator=(error const&) = delete; 87 | 88 | private: 89 | error(); 90 | 91 | public: 92 | bool debug_flag; 93 | bool warning_flag; 94 | 95 | private: 96 | std::vector errors; 97 | 98 | bool has_minor_errors_; 99 | }; 100 | 101 | } -------------------------------------------------------------------------------- /code/include/common/token.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Location 6 | { 7 | std::string file; 8 | int line, col; 9 | }; 10 | 11 | enum class TokenType 12 | { 13 | EXPR, 14 | 15 | OPEN_BRACKET, 16 | CLOSE_BRACKET, 17 | OPEN_SQUARE, 18 | CLOSE_SQUARE, 19 | OPEN_CURLY, 20 | CLOSE_CURLY, 21 | COLON, 22 | SEMICOLON, 23 | COMMA, 24 | 25 | UNARY_OPERATOR, 26 | BINARY_OPERATOR, 27 | 28 | ASSIGN, // = 29 | ASSIGN_OPERATOR, // +=, -=, *=, /=, %= 30 | 31 | BOOL_LIT, 32 | CHAR_LIT, 33 | INT_LIT, 34 | FLOAT_LIT, 35 | STRING_LIT, 36 | 37 | VARIABLE, 38 | 39 | TYPE, 40 | 41 | IF, 42 | ELIF, 43 | ELSE, 44 | 45 | FOR, 46 | WHILE, 47 | 48 | DEF, 49 | VOID, 50 | RETURN, 51 | 52 | END_OF_FILE 53 | }; 54 | 55 | struct Token 56 | { 57 | TokenType type; 58 | std::string str; 59 | 60 | Location loc; 61 | }; 62 | 63 | namespace night 64 | { 65 | 66 | std::string to_str(TokenType type); 67 | 68 | } -------------------------------------------------------------------------------- /code/include/common/type.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Provides the Type structure used by the front end. In the front end, the 3 | * actual values are not important, as only the type information is needed. 4 | * This is used by the parser and type checking. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | /* 13 | * There are two categories of types, 14 | * Primitive Types 15 | * Arrays 16 | * 17 | * Primitive types include bool, char, int, and float, and are represented as 18 | * dimension as well. 19 | */ 20 | struct Type 21 | { 22 | enum Primitive { 23 | BOOL, 24 | CHAR, 25 | INT, 26 | FLOAT 27 | } prim; 28 | 29 | int dim; 30 | 31 | bool is_prim() const; 32 | bool is_str() const; 33 | bool is_arr() const; 34 | 35 | Type() = default; 36 | 37 | Type(std::string const& _type, int _dim = 0); 38 | Type(Primitive _prim, int _dim = 0); 39 | Type(Type const& _other); 40 | 41 | bool operator==(Primitive _prim) const; 42 | bool operator==(Type const& _type) const; 43 | }; 44 | 45 | namespace night 46 | { 47 | 48 | std::string to_str(Type const& type); 49 | 50 | } -------------------------------------------------------------------------------- /code/include/common/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace night { 7 | 8 | template 9 | void container_insert(ContainerDest& dest, ContainerSrc src, std::size_t position) 10 | { 11 | assert(position <= dest.size()); 12 | 13 | auto it = std::begin(dest); 14 | std::advance(it, position); 15 | 16 | dest.insert(it, std::begin(src), std::end(src)); 17 | } 18 | 19 | template 20 | void container_concat(ContainerDest& dest, ContainerSrc const& src) 21 | { 22 | dest.insert(std::end(dest), std::cbegin(src), std::cend(src)); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /code/include/interpreter/interpreter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "interpreter_scope.hpp" 4 | #include "common/bytecode.hpp" 5 | #include "common/debug.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | std::optional interpret_bytecodes( 18 | InterpreterScope& scope, 19 | bytecodes_t const& codes, 20 | bool is_global, 21 | char* buf = NULL 22 | ); 23 | 24 | // template can either be int64_t or uint64_t 25 | // iterator 26 | // start: int code type 27 | // end: last code of int 28 | template 29 | T interpret_int(bytecodes_t::const_iterator& it, unsigned short size) 30 | { 31 | T num = 0; 32 | for (unsigned short i = 0; i < size; ++i) 33 | { 34 | T byte = *(++it); 35 | for (unsigned short j = 0; j < i; ++j) 36 | byte <<= 8; 37 | 38 | num |= byte; 39 | } 40 | 41 | return num; 42 | } 43 | 44 | double interpret_flt(bytecodes_t::const_iterator& it, unsigned short size); 45 | 46 | char* interpret_predefined_input(); 47 | 48 | // iterator 49 | // start: bytecode type 50 | // end: last code of float 51 | void push_float(std::stack& s, bytecodes_t::const_iterator& it, int count); 52 | 53 | void push_str(std::stack& s); 54 | void push_arr(std::stack& s); 55 | void push_arr_and_fill(std::stack& s); 56 | void fill_arr(intpr::Value& arr, std::stack& s, std::vector const& dimensions, int current_dimension); 57 | 58 | void push_subscript(std::stack& s, bool is_string); 59 | 60 | void push_string_input(std::stack& s); 61 | 62 | intpr::Value pop(std::stack& s); 63 | 64 | char* night_get_line(); 65 | -------------------------------------------------------------------------------- /code/include/interpreter/interpreter_scope.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/bytecode.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace intpr 9 | { 10 | 11 | struct Value; 12 | 13 | struct Array 14 | { 15 | Value* data; 16 | std::size_t size; 17 | }; 18 | 19 | struct Value 20 | { 21 | union { 22 | int64_t i; 23 | uint64_t ui; 24 | double d; 25 | char* s; 26 | Array a; 27 | } as; 28 | 29 | Value() = default; 30 | Value(int64_t _i); 31 | Value(uint64_t _ui); 32 | Value(double _d); 33 | Value(char* _s); 34 | Value(Array _a); 35 | Value(Value const& _v); 36 | ~Value(); 37 | }; 38 | 39 | } // intpr:: 40 | 41 | namespace night { 42 | using id_t = uint64_t; 43 | } 44 | 45 | using var_container = std::unordered_map; 46 | 47 | struct InterpreterFunction; 48 | using func_container = std::unordered_map; 49 | 50 | struct InterpreterFunction 51 | { 52 | std::vector param_ids; 53 | bytecodes_t codes; 54 | }; 55 | 56 | class InterpreterScope 57 | { 58 | public: 59 | static func_container funcs; 60 | 61 | InterpreterScope(); 62 | InterpreterScope(InterpreterScope* _parent); 63 | 64 | intpr::Value& get_variable(night::id_t id); 65 | 66 | void set_variable(night::id_t id, intpr::Value const& val); 67 | 68 | static InterpreterScope* global_scope; 69 | 70 | private: 71 | bool has_variable(night::id_t id); 72 | 73 | private: 74 | InterpreterScope* parent; 75 | var_container vars; 76 | }; 77 | -------------------------------------------------------------------------------- /code/include/language.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum PredefinedFunctions 6 | { 7 | PRINT_BOOL, 8 | PRINT_CHAR, 9 | PRINT_INT, 10 | PRINT_FLOAT, 11 | PRINT_STR, 12 | 13 | INPUT, 14 | 15 | INT_TO_CHAR, 16 | STR_TO_CHAR, 17 | 18 | BOOL_TO_INT, 19 | CHAR_TO_INT, 20 | FLOAT_TO_INT, 21 | STR_TO_INT, 22 | 23 | BOOL_TO_FLOAT, 24 | CHAR_TO_FLOAT, 25 | INT_TO_FLOAT, 26 | STR_TO_FLOAT, 27 | 28 | CHAR_TO_STR, 29 | INT_TO_STR, 30 | FLOAT_TO_STR, 31 | 32 | LEN, 33 | }; 34 | 35 | int bool_to_int(bool b); 36 | 37 | int char_to_int(char c); 38 | 39 | int float_to_int(double d); 40 | 41 | long long str_to_int(char* s); 42 | 43 | float bool_to_float(bool b); 44 | 45 | float char_to_float(char c); 46 | 47 | float int_to_float(int i); 48 | 49 | float str_to_float(char* s); 50 | 51 | char* char_to_str(char c); 52 | 53 | char* int_to_str(int i); 54 | 55 | char* float_to_str(float f); 56 | 57 | int len(char* s); 58 | -------------------------------------------------------------------------------- /code/include/lexer/lexer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/token.hpp" 4 | #include "common/error.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct Location; 12 | 13 | class Lexer 14 | { 15 | public: 16 | Lexer() = default; 17 | Lexer(std::string const& _file_name); 18 | ~Lexer(); 19 | 20 | public: 21 | Token const& eat(); 22 | Token peek(); 23 | Token const& curr() const; 24 | 25 | // Eats token, and checks the type of new current token. Returns current if 26 | // successful. 27 | Token const& expect( 28 | TokenType type, 29 | std::string const& err = "\n", 30 | std::source_location const& s_loc = std::source_location::current()); 31 | 32 | Token const& curr_is( 33 | TokenType type, 34 | std::string const& err_msg = "", 35 | std::source_location const& s_loc = std::source_location::current()); 36 | 37 | // Used for testing. 38 | void scan_code(std::string const& code); 39 | 40 | private: 41 | Token eat_string(); 42 | Token eat_character(); 43 | Token eat_keyword(); 44 | Token eat_number(); 45 | Token eat_symbol(); 46 | 47 | // returns false when it reaches end of file 48 | bool new_line(); 49 | Token eat_new_line(); 50 | 51 | public: 52 | Location loc; 53 | 54 | private: 55 | std::fstream file; 56 | std::string file_line; 57 | 58 | Token curr_tok; 59 | std::optional prev_tok; 60 | }; -------------------------------------------------------------------------------- /code/include/parse_args.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Parses command line arguments and returns file name. 6 | std::string parse_args(int argc, char* argv[]); 7 | -------------------------------------------------------------------------------- /code/include/parser/ast/expression.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Expressions in Night are represented as ASTs consisting of operators, 3 | * variables, function calls, and values. 4 | * 5 | * Common error: When coding the copy constructor, not all the member variables 6 | * are copied over. 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "parser/statement_scope.hpp" 13 | #include "common/bytecode.hpp" 14 | #include "common/type.hpp" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace expr 22 | { 23 | 24 | class Expression; 25 | using expr_p = std::shared_ptr; 26 | 27 | class Array; 28 | 29 | 30 | /* 31 | * For each Expression, the following steps must be performed in order, 32 | * 1. Insert a node for expression parsing 33 | * 2. Type check its children and itself 34 | * 3. Optimize its children and itself 35 | * 4. Generate bytecodes 36 | */ 37 | class Expression 38 | { 39 | public: 40 | /* 41 | * @param _loc Location of error messages used in optimize() and 42 | * generate_codes(). 43 | * @param _precedence_ Of the operator if applicable.. 44 | */ 45 | Expression( 46 | Location const& _loc, 47 | int _precedence_ 48 | ); 49 | 50 | /* Inserts a node into the AST. There are three cases: 51 | * 1) Node needs to be inserted in the current position. 52 | * In this case, 'prev' will be assigned to the node, and then the node 53 | * will be assigned to the current position. 54 | * 2) Node needs to be inserted as a child. 55 | * In this case, the 'node' will be appropriately inserted as a child node. 56 | * 3) Node needs to be inserted further down the AST. 57 | * In this case, the 'node' will be passed down to the children using 58 | * this->child.insert(node). 59 | * 60 | * @param node The new node to be inserted into the AST. 61 | * @param prev The connection between the higher node and this. It is nullptr if 62 | * this is the head node. 63 | */ 64 | virtual void insert_node( 65 | expr_p node, 66 | expr_p* prev = nullptr 67 | ) = 0; 68 | 69 | /* 70 | * @returns the precedence. 71 | */ 72 | int precedence() const; 73 | 74 | /* 75 | * When guard is set, no new node can be inserted as a child of this node. 76 | * In other words it treats this node as one with the highest precedence. 77 | * This is used for brackets. 78 | */ 79 | void set_guard(); 80 | 81 | /* 82 | * Returns the type of the expression. If std::nullopt is returned, then 83 | * type_check() has failed and at least one minor error has been created. 84 | * 85 | * No fatal errors should be thrown here. 86 | * 87 | * For testing, even though some cases the expression type doesn't need to 88 | * be tested (eg. test case already ensures proper types), this function 89 | * still needs to be called as many member variable are initialized here. 90 | */ 91 | virtual std::optional type_check( 92 | StatementScope& scope 93 | ) noexcept = 0; 94 | 95 | /* Evaluates constant expressions and returns the new expression. Leaves 96 | * non-constant expressions unchanged. 97 | * Usage: 98 | * expr = expr->optimize(scope); 99 | * 100 | * @param scope The scope in which the expression lies. 101 | */ 102 | [[nodiscard]] 103 | virtual expr_p optimize( 104 | StatementScope const& scope 105 | ) = 0; 106 | 107 | /* 108 | * @returns The bytecode representation of the expression. 109 | */ 110 | virtual bytecodes_t generate_codes() const = 0; 111 | 112 | protected: 113 | constexpr static int single_precedence = 1000; 114 | constexpr static int unary_precedence = 100; 115 | constexpr static int binary_precedence = 10; 116 | 117 | Location loc; 118 | int precedence_; 119 | }; 120 | 121 | 122 | class Variable : public Expression 123 | { 124 | public: 125 | Variable( 126 | Location const& _loc, 127 | std::string const& _name, 128 | std::optional const& _id = std::nullopt 129 | ); 130 | 131 | void insert_node( 132 | expr_p node, 133 | expr_p* prev = nullptr 134 | ) override; 135 | 136 | std::optional type_check( 137 | StatementScope& scope 138 | ) noexcept override; 139 | 140 | /* 141 | * No optimization done, just returns itself. Constant variables can be 142 | * optimized as their values, but constants don't exist in Night yet. 143 | */ 144 | [[nodiscard]] 145 | expr_p optimize( 146 | StatementScope const& scope 147 | ) override; 148 | 149 | bytecodes_t generate_codes() const override; 150 | 151 | private: 152 | std::string name; 153 | 154 | std::optional id; 155 | }; 156 | 157 | 158 | class Array : public Expression 159 | { 160 | public: 161 | Array( 162 | Location const& _loc, 163 | std::vector const& _elements, 164 | bool _is_str_ 165 | ); 166 | 167 | void insert_node( 168 | expr_p node, 169 | expr_p* prev = nullptr 170 | ) override; 171 | 172 | std::optional type_check( 173 | StatementScope& scope 174 | ) noexcept override; 175 | 176 | /* 177 | * Optimizes each element of the array. 178 | */ 179 | [[nodiscard]] 180 | expr_p optimize( 181 | StatementScope const& scope 182 | ) override; 183 | 184 | bytecodes_t generate_codes() const override; 185 | 186 | bool is_str() const; 187 | 188 | public: 189 | std::vector elements; 190 | 191 | // Note: Strings are stored as character arrays, but have special 192 | // properties such as being able to work with addition. 193 | bool is_str_; 194 | }; 195 | 196 | 197 | /* 198 | * Array element access. 199 | */ 200 | class Allocate : public Expression 201 | { 202 | public: 203 | Allocate( 204 | Location const& _loc, 205 | Type::Primitive const _type, 206 | std::vector const& _sizes 207 | ); 208 | 209 | void insert_node( 210 | expr_p node, 211 | expr_p* prev = nullptr 212 | ) override; 213 | 214 | std::optional type_check( 215 | StatementScope& scope 216 | ) noexcept override; 217 | 218 | /* 219 | * Optimizes each size of the array allocation. 220 | */ 221 | [[nodiscard]] 222 | expr_p optimize( 223 | StatementScope const& scope 224 | ) override; 225 | 226 | bytecodes_t generate_codes() const override; 227 | 228 | public: 229 | Type::Primitive type; 230 | std::vector sizes; 231 | }; 232 | 233 | 234 | class Numeric : public Expression 235 | { 236 | public: 237 | Numeric( 238 | Location const& _loc, 239 | Type::Primitive _type, 240 | std::variant const& _val 241 | ); 242 | 243 | void insert_node( 244 | expr_p node, 245 | expr_p* prev = nullptr 246 | ) override; 247 | 248 | std::optional type_check( 249 | StatementScope& scope 250 | ) noexcept override; 251 | 252 | /* 253 | * Returns itself for optimization. 254 | */ 255 | [[nodiscard]] 256 | expr_p optimize( 257 | StatementScope const& scope 258 | ) override; 259 | 260 | bytecodes_t generate_codes() const override; 261 | 262 | bool is_true() const; 263 | 264 | public: 265 | std::variant const& get_val() const; 266 | 267 | public: 268 | std::variant val; 269 | Type::Primitive type; 270 | 271 | private: 272 | }; 273 | 274 | } 275 | -------------------------------------------------------------------------------- /code/include/parser/ast/expression_operator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "expression.hpp" 4 | 5 | namespace expr 6 | { 7 | 8 | enum class UnaryOpType 9 | { 10 | NEGATIVE, 11 | NOT 12 | }; 13 | 14 | struct UnaryOp : public Expression 15 | { 16 | public: 17 | UnaryOp( 18 | Location const& _loc, 19 | std::string const& _operator 20 | ); 21 | 22 | UnaryOp( 23 | UnaryOp const& other 24 | ); 25 | 26 | void insert_node( 27 | expr_p node, 28 | expr_p* prev = nullptr 29 | ) override; 30 | 31 | std::optional type_check( 32 | StatementScope& scope 33 | ) noexcept override; 34 | 35 | /* 36 | * Optimizes the expression, and if it is Numeric, then evaluates the 37 | * expression. 38 | */ 39 | [[nodiscard]] 40 | expr_p optimize( 41 | StatementScope const& scope 42 | ) override; 43 | 44 | /* 45 | * Generates bytes in the following order, 46 | * 1) Expression 47 | * 3) Operator 48 | * 49 | * expr and expr_type must be initialized before this function is called. 50 | */ 51 | bytecodes_t generate_codes() const override; 52 | 53 | private: 54 | /* 55 | * Returns the byte type of the operator. 56 | * 57 | * Used in generate_codes(). 58 | */ 59 | bytecode_t generate_operator_byte() const; 60 | 61 | std::string operator_type_to_str() const; 62 | 63 | private: 64 | // Given an operator as a string, provides the operator type. 65 | static std::unordered_map const operators; 66 | 67 | // Initialized in constructor. 68 | UnaryOpType operator_type; 69 | 70 | // Initialized in insert_node(). 71 | expr_p expr; 72 | 73 | // Initialized in type_check(). 74 | // Used to determine type of operator bytecode in generate_codes(). 75 | std::optional expr_type; 76 | }; 77 | 78 | 79 | enum class BinaryOpType 80 | { 81 | ADD, SUB, MULT, DIV, MOD, 82 | LESSER, GREATER, 83 | LESSER_EQUALS, GREATER_EQUALS, 84 | EQUALS, NOT_EQUALS, 85 | AND, OR, 86 | SUBSCRIPT 87 | }; 88 | 89 | class BinaryOp : public Expression 90 | { 91 | public: 92 | BinaryOp( 93 | Location const& _loc, 94 | std::string const& _operator 95 | ); 96 | 97 | BinaryOp( 98 | BinaryOp const& other 99 | ); 100 | 101 | void insert_node( 102 | expr_p node, 103 | expr_p* prev = nullptr 104 | ) override; 105 | 106 | std::optional type_check( 107 | StatementScope& scope 108 | ) noexcept override; 109 | 110 | /* 111 | * Optimizes in the following order, 112 | * 1) Optimizes left and right hand expressions 113 | * 2) If both are strings and operator is ADD, then perform string 114 | * concatenation. 115 | * 2) If both are Numeric, evaluate the binary expression 116 | * 3) Otherwise, return self 117 | * 118 | * lhs and rhs must be initialized before this function is called. 119 | */ 120 | [[nodiscard]] 121 | expr_p optimize( 122 | StatementScope const& scope 123 | ) override; 124 | 125 | /* 126 | * Generates bytes in the following order, 127 | * 1) Left hand side and its type cast if it has one 128 | * 2) Right hand side and its type cast if it has one 129 | * 3) Operator 130 | * 131 | * lhs, lhs_type, rhs and rhs_type must be initialized before this function is called. 132 | */ 133 | bytecodes_t generate_codes() const override; 134 | 135 | public: 136 | BinaryOpType const& get_type() const; 137 | 138 | expr::expr_p const& get_lhs() const; 139 | 140 | expr::expr_p const& get_rhs() const; 141 | 142 | private: 143 | /* 144 | * String concatenation only works with the addition operator on two strings. 145 | */ 146 | std::optional type_check_string_concat(); 147 | 148 | /* 149 | * Arithmetic only works on two primitives. 150 | */ 151 | std::optional type_check_arithmetic(); 152 | 153 | /* 154 | * Modulus only works on two integers. 155 | */ 156 | std::optional type_check_mod() const; 157 | 158 | /* 159 | * Comparisons only works on two non-arrays. 160 | */ 161 | std::optional type_check_comparision(); 162 | 163 | /* 164 | * Booleans only works on two primitives. 165 | */ 166 | std::optional type_check_boolean(); 167 | 168 | /* 169 | * Subscript only works with integer indices on an array or string. 170 | */ 171 | std::optional type_check_subscript() const; 172 | 173 | /* 174 | * Returns a pair of string Arrays if operator is ADD and both left and right 175 | * hand side expressions are strings. Otherwise return a pair of NULLs. 176 | * 177 | * Used in optimize(). 178 | */ 179 | std::pair, std::shared_ptr> is_string_concatenation() const; 180 | 181 | /* 182 | * Returns the index and string if operator is SUBSCRIPT and left hand side 183 | * expression is a numeric and right hand side expression is an array. 184 | * Otherwise return a pair of NULLs. 185 | * 186 | * Used in optimize(). 187 | */ 188 | std::pair, std::shared_ptr> is_array_subscript() const; 189 | 190 | /* 191 | * Returns the byte type of the operator. 192 | * 193 | * Used in generate_codes(). 194 | */ 195 | bytecode_t generate_operator_byte() const; 196 | 197 | std::string operator_type_to_str() const; 198 | 199 | private: 200 | // Given an operator as a string, provides the precedence and operator type. 201 | static std::unordered_map> const operators; 202 | 203 | // Initialized in constructor. 204 | BinaryOpType operator_type; 205 | 206 | // Initialized in insert_node(). 207 | expr_p lhs, rhs; 208 | 209 | // Initialized in type_check(). 210 | // Used to determine type of operator bytecode in generate_codes(). 211 | std::optional lhs_type, rhs_type; 212 | }; 213 | 214 | } // night:: 215 | -------------------------------------------------------------------------------- /code/include/parser/ast/statement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser/statement_scope.hpp" 4 | #include "parser/ast/expression.hpp" 5 | #include "common/bytecode.hpp" 6 | #include "common/type.hpp" 7 | #include "common/token.hpp" 8 | #include "common/error.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class Statement; 16 | using stmt_p = std::shared_ptr; 17 | 18 | using conditional_container = std::vector< 19 | std::pair> 20 | >; 21 | 22 | 23 | /* This class represents *all* valid statements in Night. 24 | * 25 | * check() MUST be called before optimize() and generate_codes(). 26 | * 27 | * Usage: 28 | * statement->check(scope); 29 | * 30 | * if (night::error::get().has_minor_errors()) 31 | * throw night::error::get(); 32 | * 33 | * if (statement->optimize(scope)) 34 | * auto codes = statement->generate_codes(); 35 | */ 36 | class Statement 37 | { 38 | public: 39 | /* This method MUST be called before optimize() and generate_codes(). 40 | * 41 | * The purpose of this method is to ensure the statement is correct for 42 | * optimizing and generating bytecodes. After this method is called, and if 43 | * there are no minor errors, then it is guaranteed for optimize() and 44 | * generate_codes() to work. 45 | * 46 | * This method also initializes certain std::optional member variables, 47 | * such as variable id for the VariableInit statement. 48 | */ 49 | virtual void check(StatementScope& global_scope) = 0; 50 | 51 | /* Return true to keep the statement. Return false to delete the statement. 52 | * A statement should be deleted if it's redundant, for example unused 53 | * variables or loops that never run. 54 | * Sample Usage: 55 | * if (!stmt->optimize(global_scope)) 56 | * remove stmt; 57 | */ 58 | virtual bool optimize(StatementScope& global_scope) = 0; 59 | 60 | virtual bytecodes_t generate_codes() const = 0; 61 | }; 62 | 63 | 64 | /* 65 | * Initialization of non-array variables. 66 | * 67 | * When expression is null, a default value will be provided respectful to the 68 | * type. 69 | * 70 | * Examples, 71 | * my_var int; 72 | * my_var int = 3; 73 | */ 74 | class VariableInit : public Statement 75 | { 76 | public: 77 | VariableInit( 78 | std::string const& _name, 79 | Location const& _name_loc, 80 | std::string const& _type, 81 | expr::expr_p const& _expr 82 | ); 83 | 84 | void check( 85 | StatementScope& scope 86 | ) override; 87 | 88 | bool optimize( 89 | StatementScope& scope 90 | ) override; 91 | 92 | /* 93 | * Bytes are generated in the following order, 94 | * 1) Expression bytes 95 | * 2) ID bytes 96 | * 3) STORE 97 | */ 98 | bytecodes_t generate_codes() const override; 99 | 100 | private: 101 | std::string name; 102 | Location name_loc; 103 | 104 | Type type; 105 | expr::expr_p expr; 106 | 107 | // Initialized in check(). 108 | std::optional id; 109 | 110 | // Initialized in check(). 111 | std::optional expr_type; 112 | }; 113 | 114 | 115 | /* 116 | * Initialization of arrays. 117 | * 118 | * The dimensions of the array are purely for type checking, and do not impact 119 | * bytecode generation. 120 | * 121 | * Examples, 122 | * my_var int[2][3]; 123 | * my_var int[] = [ 1, 2, 3 ]; 124 | */ 125 | class ArrayInitialization : public Statement 126 | { 127 | public: 128 | ArrayInitialization( 129 | std::string const& _name, 130 | Location const& _name_loc, 131 | std::string const& _type, 132 | std::vector const& _arr_sizes, 133 | expr::expr_p const& _expr 134 | ); 135 | 136 | void check( 137 | StatementScope& scope 138 | ) override; 139 | 140 | bool optimize( 141 | StatementScope& scope 142 | ) override; 143 | 144 | /* 145 | * Bytes are generated in the following order, 146 | * 1) Expression bytes 147 | * 2) ID bytes 148 | * 3) STORE 149 | */ 150 | bytecodes_t generate_codes() const override; 151 | 152 | // Precondition: 153 | // 'expr' is not null 154 | // 'type' is to set type_conversions for Array 155 | void fill_array(Type const& type, expr::expr_p expr, int depth) const; 156 | 157 | private: 158 | std::string name; 159 | Location name_loc; 160 | 161 | Type type; 162 | std::vector arr_sizes; 163 | std::vector arr_sizes_numerics; 164 | expr::expr_p expr; 165 | 166 | // Initialized in check(). 167 | std::optional id; 168 | 169 | // Initialized in check(). 170 | std::optional expr_type; 171 | 172 | // true 173 | // my_arr int[3] = [ 1, 2, 3 ]; 174 | // false 175 | // my_arr int[] = int[my_size]; 176 | bool is_static; 177 | }; 178 | 179 | 180 | class VariableAssign : public Statement 181 | { 182 | public: 183 | VariableAssign( 184 | std::string const& _name, 185 | Location const& _name_loc, 186 | std::string const& _assign_op, 187 | expr::expr_p const& _expr 188 | ); 189 | 190 | void check(StatementScope& scope) override; 191 | bool optimize(StatementScope& scope) override; 192 | bytecodes_t generate_codes() const override; 193 | 194 | private: 195 | std::string name; 196 | Location name_loc; 197 | 198 | std::string assign_op; 199 | expr::expr_p expr; 200 | 201 | // Initialized in check(). 202 | StatementVariable variable; 203 | 204 | // Initialized in check(). 205 | std::optional expr_type; 206 | }; 207 | 208 | 209 | class Conditional : public Statement 210 | { 211 | public: 212 | Conditional( 213 | Location const& _loc, 214 | conditional_container const& _conditionals 215 | ); 216 | 217 | void check(StatementScope& scope) override; 218 | bool optimize(StatementScope& scope) override; 219 | 220 | /** 221 | * CONDITION boolean expression for conditional 222 | * INT8 8 bit integer value for JUMP_IF_FALSE 223 | * JUMP_IF_FALSE jumps to first line after JUMP 224 | * ... conditional code 225 | * INT8 8 bit integer value for JUMP 226 | * JUMP jumps to first line after conditional chain 227 | */ 228 | bytecodes_t generate_codes() const override; 229 | 230 | private: 231 | Location loc; 232 | 233 | // If a conditional epxression is nullptr, then it represents and else 234 | // statement and should be treated the same as a true if statement. 235 | conditional_container conditionals; 236 | }; 237 | 238 | 239 | class While : public Statement 240 | { 241 | public: 242 | While( 243 | Location const& _loc, 244 | expr::expr_p const& _cond, 245 | std::vector const& _block 246 | ); 247 | 248 | void check(StatementScope& scope) override; 249 | bool optimize(StatementScope& scope) override; 250 | 251 | /** 252 | * CONDITION boolean expression for while loop condition 253 | * INT8 8 bit integer value for JUMP_IF_FALSE 254 | * JUMP_IF_FALSE jumps to first line after JUMP_N 255 | * ... while loop code 256 | * INT8 8 bit integer value for JUMP_N 257 | * JUMP_N jumps to first line of CONDITION 258 | */ 259 | bytecodes_t generate_codes() const override; 260 | 261 | private: 262 | Location loc; 263 | 264 | expr::expr_p cond_expr; 265 | std::vector block; 266 | }; 267 | 268 | 269 | class For : public Statement 270 | { 271 | public: 272 | // params: 273 | // _block should already include VariableAssign statement 274 | For( 275 | Location const& _loc, 276 | VariableInit const& _var_init, 277 | expr::expr_p const& _cond_expr, 278 | std::vector const& _block 279 | ); 280 | 281 | void check(StatementScope& scope) override; 282 | bool optimize(StatementScope& scope) override; 283 | bytecodes_t generate_codes() const override; 284 | 285 | private: 286 | VariableInit var_init; 287 | While loop; 288 | 289 | StatementScope local_scope; 290 | }; 291 | 292 | 293 | struct Parameter 294 | { 295 | Parameter( 296 | std::string const& _name, 297 | Type const& _type, 298 | Location const& _location 299 | ); 300 | 301 | std::string name; 302 | Type type; 303 | 304 | Location location; 305 | }; 306 | 307 | class Function : public Statement 308 | { 309 | public: 310 | Function( 311 | std::string const& _name, 312 | Location const& _name_location, 313 | std::vector const& _parameters, 314 | std::string const& _rtn_type, 315 | int rtn_type_dim, 316 | std::vector const& _body 317 | ); 318 | 319 | void check(StatementScope& scope) override; 320 | bool optimize(StatementScope& scope) override; 321 | bytecodes_t generate_codes() const override; 322 | 323 | private: 324 | std::string name; 325 | Location name_location; 326 | 327 | std::vector parameters; 328 | 329 | std::optional rtn_type; 330 | 331 | std::vector body; 332 | 333 | // Initialized in check(). 334 | std::optional id; 335 | 336 | // Initialized in check(). 337 | std::vector parameter_ids; 338 | }; 339 | 340 | 341 | class Return : public Statement 342 | { 343 | public: 344 | Return( 345 | Location const& _loc, 346 | expr::expr_p const& _expr 347 | ); 348 | 349 | void check(StatementScope& scope) override; 350 | bool optimize(StatementScope& scope) override; 351 | bytecodes_t generate_codes() const override; 352 | 353 | private: 354 | Location loc; 355 | 356 | expr::expr_p expr; 357 | }; 358 | 359 | 360 | class ArrayMethod : public Statement 361 | { 362 | public: 363 | ArrayMethod( 364 | Location const& _loc, 365 | std::string const& _var_name, 366 | std::string const& _assign_op, 367 | std::vector const& _subscripts, 368 | expr::expr_p const& _assign_expr 369 | ); 370 | 371 | void check(StatementScope& scope) override; 372 | bool optimize(StatementScope& scope) override; 373 | bytecodes_t generate_codes() const override; 374 | 375 | private: 376 | std::string var_name; 377 | Location name_loc; 378 | 379 | std::string assign_op; 380 | std::vector subscripts; 381 | expr::expr_p assign_expr; 382 | 383 | std::optional id; 384 | std::optional assign_type; 385 | }; 386 | 387 | 388 | namespace expr { 389 | 390 | class FunctionCall : public Statement, public expr::Expression 391 | { 392 | public: 393 | FunctionCall( 394 | Token const& _name, 395 | std::vector const& _arg_exprs, 396 | std::optional const& _id = std::nullopt 397 | ); 398 | 399 | void insert_node( 400 | expr_p node, 401 | expr_p* prev = nullptr); 402 | 403 | void check(StatementScope& scope) override; 404 | std::optional type_check(StatementScope& scope) noexcept override; 405 | bool optimize(StatementScope& scope) override; 406 | [[nodiscard]] 407 | expr_p optimize(StatementScope const& scope) override; 408 | bytecodes_t generate_codes() const override; 409 | 410 | private: 411 | Token name; 412 | 413 | std::vector arg_exprs; 414 | 415 | std::optional id; 416 | 417 | bool is_expr; 418 | }; 419 | 420 | } -------------------------------------------------------------------------------- /code/include/parser/code_gen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ast/statement.hpp" 4 | #include "common/bytecode.hpp" 5 | 6 | #include 7 | 8 | bytecodes_t code_gen(std::vector& block); -------------------------------------------------------------------------------- /code/include/parser/expression_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "lexer/lexer.hpp" 4 | #include "ast/expression.hpp" 5 | 6 | #include 7 | 8 | /* If there is no expression, it is the callers responsibility to handle, or 9 | * set 'err_on_empty' to true to display an error message in that event 10 | * Lexer 11 | * start: first token before expression 12 | * end: last token after expression 13 | */ 14 | expr::expr_p parse_expr( 15 | Lexer& lexer, 16 | bool err_on_empty, 17 | std::optional const& end_token = std::nullopt 18 | ); 19 | 20 | expr::expr_p parse_string(Lexer& lexer); 21 | 22 | expr::expr_p parse_variable_or_call(Lexer& lexer); 23 | 24 | expr::expr_p parse_subscript_or_array(Lexer& lexer, std::optional previous_token_type); 25 | 26 | expr::expr_p parse_subtract_or_negative(Lexer& lexer, std::optional previous_token_type); 27 | 28 | expr::expr_p parse_bracket(Lexer& lexer, bool err_on_empty); 29 | 30 | void parse_check_value(std::optional const& previous_type, Lexer const& lexer); 31 | 32 | void parse_check_unary_operator(std::optional const& previous_type, Lexer const& lexer); 33 | 34 | void parse_check_binary_operator(std::optional const& previous_type, Lexer const& lexer); 35 | 36 | void parse_check_open_square(std::optional const& previous_type, Lexer const& lexer); 37 | 38 | void parse_check_open_bracket(std::optional const& previous_type, Lexer const& lexer); 39 | 40 | void parse_check_expression_ending(std::optional const& previous_type, Lexer const& lexer); 41 | -------------------------------------------------------------------------------- /code/include/parser/statement_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "lexer/lexer.hpp" 4 | #include "ast/statement.hpp" 5 | #include "ast/expression.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /* 12 | * Used to reserve space in vector's used to store dimensions or 13 | * subscripts. 14 | * 15 | * The average array dimension is 3. 16 | */ 17 | constexpr short AVG_ARRAY_DIMENSION = 3; 18 | 19 | /* The starting function for parsing a file. 20 | */ 21 | std::vector parse_file(std::string const& main_file); 22 | 23 | /* Parses a body of statements. 24 | * Lexer 25 | * start: first token of statements 26 | * end: first token of next statement 27 | * 28 | * @param contains_return_stmt will be set to true if the statement block 29 | * contains a return statement. This is for non-void functions to check if 30 | * their body contains a returns statement. 31 | */ 32 | std::vector parse_stmts(Lexer& lexer, bool requires_curly, bool* contains_return = nullptr); 33 | 34 | /* Parses a singular statement. Note that a singular statement could also be 35 | * the only statement in a scope. 36 | * Lexer 37 | * start: first token of statement 38 | * end: first token of next statement 39 | */ 40 | stmt_p parse_stmt(Lexer& lexer, bool* contains_return = nullptr); 41 | 42 | /* This function handles three cases: 43 | * variable initialization 44 | * variable assignment 45 | * function calls 46 | * Lexer: 47 | * start: variable name 48 | * end: first token of next statement 49 | */ 50 | stmt_p parse_var(Lexer& lexer); 51 | 52 | /* 53 | * Lexer starts at variable type and ends at semicolon. 54 | * 55 | * Examples, 56 | * my_var int; 57 | * my_var int = ; 58 | */ 59 | VariableInit parse_variable_initialization( 60 | Lexer& lexer, 61 | Token const& name 62 | ); 63 | 64 | /* 65 | * Lexer starts at variable type and ends at semicolon. 66 | * 67 | * Examples, 68 | * my_var int[]; 69 | * my_var int[] = []; 70 | */ 71 | ArrayInitialization parse_array_initialization( 72 | Lexer& lexer, 73 | Token const& name 74 | ); 75 | 76 | /* 77 | * It is the callers responsibility to check if lexer.curr() is their expected 78 | * token after this function is called. 79 | * 80 | * Lexer starts at assignment operator and ends at first token of next 81 | * statement. 82 | * 83 | * Examples, 84 | * my_var = ; 85 | * for (;; my_var += ) {} 86 | */ 87 | VariableAssign parse_variable_assignment( 88 | Lexer& lexer, 89 | Token const& name 90 | ); 91 | 92 | /* 93 | * Lexer starts at variable name and ends at first token of next statement. 94 | * 95 | * Examples, 96 | * my_var[] += ; 97 | */ 98 | ArrayMethod parse_array_method( 99 | Lexer& lexer, 100 | Token const& name 101 | ); 102 | 103 | /* 104 | * It is the callers responsibility to check if lexer.curr() is their expected 105 | * token after this function is called. 106 | * 107 | * Lexer starts at open bracket and ends at closing bracket. 108 | * 109 | * Examples, 110 | * my_func(); 111 | * my_var = my_func(); 112 | */ 113 | expr::FunctionCall parse_func_call( 114 | Lexer& lexer, 115 | Token const& name 116 | ); 117 | 118 | /* Parses the entire conditional chain (every 'else if' and 'else' that follows) 119 | * Lexer: 120 | * start: if token 121 | * end: first token of next statement 122 | */ 123 | Conditional parse_if(Lexer& lexer, bool* contains_return = nullptr); 124 | 125 | /* Lexer: 126 | * start: while token 127 | * end: first token of next statement 128 | */ 129 | While parse_while(Lexer& lexer, bool* contains_return = nullptr); 130 | 131 | /* Lexer: 132 | * start: while token 133 | * end: first token of next statement 134 | */ 135 | For parse_for(Lexer& lexer, bool* contains_return = nullptr); 136 | 137 | /* Lexer: 138 | * start: def token 139 | * end: first token of next statement 140 | */ 141 | Function parse_func(Lexer& lexer); 142 | 143 | /* Lexer: 144 | * start: return token 145 | * end: first token of next statement 146 | */ 147 | Return parse_return(Lexer& lexer); 148 | 149 | /* Useful for function parameter types and return values as those can include 150 | * subscripts 151 | * Lexer: 152 | * start: name of parameter 153 | * end: first token after type 154 | * @returns tuple of parameter type and dimension 155 | * @returns empty tuple if lexing error 156 | */ 157 | std::tuple parse_type(Lexer& lexer); -------------------------------------------------------------------------------- /code/include/parser/statement_scope.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/bytecode.hpp" 4 | #include "common/type.hpp" 5 | #include "common/error.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct StatementVariable; 13 | struct StatementFunction; 14 | 15 | using scope_var_container = std::unordered_map; 16 | using scope_func_container = std::unordered_multimap; 17 | 18 | namespace night { 19 | 20 | using id_t = uint64_t; 21 | 22 | } // night:: 23 | 24 | struct StatementVariable 25 | { 26 | night::id_t id; 27 | Type type; 28 | 29 | // Keep track of the number of times used so unused variables can be 30 | // eliminated in the optimizing stage. 31 | unsigned times_used; 32 | }; 33 | 34 | struct StatementFunction 35 | { 36 | night::id_t id; 37 | 38 | std::vector param_names; 39 | std::vector param_types; 40 | std::optional rtn_type; 41 | }; 42 | 43 | class StatementScope 44 | { 45 | public: 46 | StatementScope(); 47 | 48 | StatementScope( 49 | StatementScope const& parent_scope 50 | ); 51 | 52 | StatementScope( 53 | StatementScope const& parent_scope, 54 | std::optional const& _return_type 55 | ); 56 | 57 | /* 58 | * Creates a new variables and returns its ID. 59 | * Returns nullopt if the variable is already defined or the maximum 60 | * allowed variables has been reached. 61 | * 62 | * @param name_location is to display the correct error location for the 63 | * variable name. 64 | */ 65 | std::optional create_variable( 66 | std::string const& name, 67 | Location const& name_location, 68 | Type const& type 69 | ); 70 | 71 | /* 72 | * Returns a pointer to the variable if the variable is defined in the scope. 73 | * Otherwise creates a minor error and returns nullptr. 74 | */ 75 | StatementVariable const* get_variable( 76 | std::string const& name, 77 | Location const& name_location 78 | ); 79 | 80 | /* 81 | * Creates a new function and returns its ID. 82 | * Returns nullopt if the function has already been defined or the maximum 83 | * allowed functions has been reached. 84 | */ 85 | static std::optional create_function( 86 | std::string const& name, 87 | Location const& name_location, 88 | std::vector const& _param_names, 89 | std::vector const& _param_types, 90 | std::optional const& _return_type 91 | ); 92 | 93 | static scope_func_container functions; 94 | 95 | std::optional return_type; 96 | 97 | private: 98 | scope_var_container variables; 99 | }; -------------------------------------------------------------------------------- /code/src/common/bytecode.cpp: -------------------------------------------------------------------------------- 1 | #include "common/bytecode.hpp" 2 | 3 | #include 4 | #include 5 | 6 | std::string night::to_str(bytecode_t type) 7 | { 8 | switch (type) 9 | { 10 | case ByteType_sINT1: return "sINT1"; 11 | case ByteType_sINT2: return "sINT2"; 12 | case ByteType_sINT4: return "sINT4"; 13 | case ByteType_sINT8: return "sINT8"; 14 | case ByteType_uINT1: return "uINT1"; 15 | case ByteType_uINT2: return "uINT2"; 16 | case ByteType_uINT4: return "uINT4"; 17 | case ByteType_uINT8: return "uINT8"; 18 | case ByteType_FLT4: return "FLT4"; 19 | case ByteType_FLT8: return "FLT8"; 20 | 21 | case BytecodeType_NEGATIVE_I: return "NEGATIVE_I"; 22 | case BytecodeType_NEGATIVE_F: return "NEGATIVE_F"; 23 | case BytecodeType_NOT_I: return "NOT_I"; 24 | case BytecodeType_NOT_F: return "NOT_F"; 25 | 26 | case BytecodeType_ADD_I: return "ADD_I"; 27 | case BytecodeType_ADD_F: return "ADD_F"; 28 | case BytecodeType_ADD_S: return "ADD_S"; 29 | case BytecodeType_SUB_I: return "SUB_I"; 30 | case BytecodeType_SUB_F: return "SUB_F"; 31 | case BytecodeType_MULT_I: return "MULT_I"; 32 | case BytecodeType_MULT_F: return "MULT_F"; 33 | case BytecodeType_DIV_I: return "DIV_I"; 34 | case BytecodeType_DIV_F: return "DIV_F"; 35 | case ByteType_MOD: return "MOD"; 36 | 37 | case BytecodeType_LESSER_I: return "LESSER_I"; 38 | case BytecodeType_LESSER_F: return "LESSER_F"; 39 | case BytecodeType_LESSER_S: return "LESSER_S"; 40 | case BytecodeType_GREATER_I: return "GREATER_I"; 41 | case BytecodeType_GREATER_F: return "GREATER_F"; 42 | case BytecodeType_GREATER_S: return "GREATER_S"; 43 | case BytecodeType_LESSER_EQUALS_I: return "LESSER_EQUALS_I"; 44 | case BytecodeType_LESSER_EQUALS_F: return "LESSER_EQUALS_F"; 45 | case BytecodeType_LESSER_EQUALS_S: return "LESSER_EQUALS_S"; 46 | case BytecodeType_GREATER_EQUALS_I: return "GREATER_EQUALS_I"; 47 | case BytecodeType_GREATER_EQUALS_F: return "GREATER_EQUALS_F"; 48 | case BytecodeType_GREATER_EQUALS_S: return "GREATER_EQUALS_S"; 49 | 50 | case BytecodeType_EQUALS_I: return "EQUALS_I"; 51 | case BytecodeType_EQUALS_F: return "EQUALS_F"; 52 | case BytecodeType_EQUALS_S: return "EQUALS_S"; 53 | case BytecodeType_NOT_EQUALS_I: return "NOT_EQUALS_I"; 54 | case BytecodeType_NOT_EQUALS_F: return "NOT_EQUALS_F"; 55 | case BytecodeType_NOT_EQUALS_S: return "NOT_EQUALS_S"; 56 | 57 | case BytecodeType_AND: return "AND"; 58 | case BytecodeType_OR: return "OR"; 59 | 60 | case BytecodeType_INDEX_S: return "INDEX_S"; 61 | case BytecodeType_INDEX_A: return "INDEX_A"; 62 | 63 | case ByteType_LOAD: return "LOAD"; 64 | case BytecodeType_LOAD_ELEM: return "LOAD_ELEM"; 65 | 66 | case ByteType_STORE: return "STORE"; 67 | case BytecodeType_STORE_INDEX_A: return "STORE_INDEX_A"; 68 | case BytecodeType_STORE_INDEX_S: return "STORE_INDEX_S"; 69 | 70 | case BytecodeType_ALLOCATE_STR: return "ALLOCATE_STR"; 71 | case BytecodeType_ALLOCATE_ARR: return "ALLOCATE_ARR"; 72 | case BytecodeType_ALLOCATE_ARR_AND_FILL: return "ALLOCATE_ARR_AND_FILL"; 73 | case BytecodeType_FREE_STR: return "FREE_STR"; 74 | case BytecodeType_FREE_ARR: return "FREE_ARR"; 75 | 76 | case BytecodeType_JUMP: return "JUMP"; 77 | case ByteType_JUMP_N: return "JUMP_N"; 78 | case BytecodeType_JUMP_IF_FALSE: return "JUMP_IF_FALSE"; 79 | 80 | case BytecodeType_RETURN: return "RETURN"; 81 | case BytecodeType_CALL: return "CALL"; 82 | 83 | default: return "UNKNOWN"; 84 | } 85 | } -------------------------------------------------------------------------------- /code/src/common/error.cpp: -------------------------------------------------------------------------------- 1 | #include "common/error.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static std::string const clear = "\033[0m"; 10 | static std::string const red = "\033[31m"; 11 | static std::string const green = "\033[32m"; 12 | static std::string const yellow = "\033[93m"; 13 | static std::string const blue = "\033[34m"; 14 | static std::string const cyan = "\033[96m"; 15 | 16 | static void colour_code_names(std::string& message, char name_punctuation, std::string const& colour) 17 | { 18 | bool inside_quote = false; 19 | std::size_t pos = 0; 20 | 21 | while ((pos = message.find(name_punctuation, pos)) != std::string::npos) 22 | { 23 | inside_quote = !inside_quote; 24 | 25 | std::string const& colour = inside_quote ? yellow : clear; 26 | 27 | message.replace(pos, 1, colour); 28 | pos += colour.length(); 29 | } 30 | } 31 | 32 | static void colour_code_types(std::string& message, std::string const& type, std::string const& colour) 33 | { 34 | std::string colour_coded_type = colour + type + clear; 35 | std::size_t pos = 0; 36 | 37 | while ((pos = message.find(type, pos)) != std::string::npos) 38 | { 39 | message.replace(pos, type.length(), colour_coded_type); 40 | pos += colour_coded_type.length(); 41 | } 42 | } 43 | 44 | static std::string error_type_to_str(ErrorType type) 45 | { 46 | switch (type) { 47 | case ErrorType::Warning: return "Warning"; 48 | case ErrorType::Minor: return "Minor Error"; 49 | case ErrorType::FatalCompile: return "Fatal Compile Error"; 50 | case ErrorType::FatalRuntime: return "Fatal Runtime Error"; 51 | default: throw std::runtime_error("Unhandled Case"); 52 | } 53 | } 54 | 55 | static std::string get_line_of_error(std::string const& file_name, int line_number) 56 | { 57 | std::string line; 58 | 59 | std::ifstream file(file_name); 60 | for (int i = 0; i < line_number; ++i) 61 | std::getline(file, line); 62 | 63 | line.erase(0, line.find_first_not_of(" \t\n\r\f\v")); 64 | 65 | return line; 66 | } 67 | 68 | night::error::error() 69 | : debug_flag(false) 70 | , has_minor_errors_(false) {} 71 | 72 | night::error& night::error::get() 73 | { 74 | static error instance; 75 | return instance; 76 | } 77 | 78 | void night::error::what(bool only_warnings) 79 | { 80 | for (auto& err : errors) 81 | { 82 | if (only_warnings && err.type != ErrorType::Warning) 83 | continue; 84 | 85 | /* Colour Code Error Message */ 86 | 87 | colour_code_names(err.message, '\'', yellow); 88 | 89 | colour_code_types(err.message, "boolean", cyan); 90 | colour_code_types(err.message, "character", cyan); 91 | colour_code_types(err.message, "integer", cyan); 92 | colour_code_types(err.message, "float", cyan); 93 | colour_code_types(err.message, "string", cyan); 94 | 95 | std::string error_line = get_line_of_error(err.location.file, err.location.line); 96 | 97 | /* Display Error Message */ 98 | 99 | // [ Minor Error ] 100 | std::cout << red << "[ " << error_type_to_str(err.type) << " ]\n" << clear; 101 | 102 | // source.night (10:52) 103 | std::cout << err.location.file << " (" << err.location.line << ":" << err.location.col << ")\n"; 104 | 105 | // night/code/src/ast/statement.cpp 53 106 | if (error::get().debug_flag) 107 | std::cout << err.source_location.file_name() << " " << err.source_location.line() << '\n'; 108 | 109 | std::cout << '\n' << err.message << "\n\n"; 110 | 111 | std::cout << " " << error_line << "\n"; 112 | 113 | int indent_size = 4; 114 | // -1 because the lexer ends one position after the token when it eats. 115 | int token_position = err.location.col - 1; 116 | 117 | if (err.token.str.empty()) 118 | { 119 | for (int i = 0; i < indent_size + token_position; ++i) 120 | std::cout << ' '; 121 | std::cout << green << "^\n\n" << clear; 122 | } 123 | else 124 | { 125 | for (int i = 0; i < indent_size + token_position - err.token.str.empty(); ++i) 126 | std::cout << ' '; 127 | std::cout << blue; 128 | for (int i = 0; i < err.token.str.length(); ++i) 129 | std::cout << "~"; 130 | std::cout << "\n\n" << clear; 131 | } 132 | } 133 | } 134 | 135 | void night::error::create_warning( 136 | std::string const& msg, 137 | Location const& loc, 138 | std::source_location const& s_loc) noexcept 139 | { 140 | error::get().errors.emplace_back(ErrorType::Warning, loc, s_loc, msg); 141 | } 142 | 143 | void night::error::create_minor_error( 144 | std::string const& msg, 145 | Location const& loc, 146 | std::source_location const& s_loc) noexcept 147 | { 148 | error::get().errors.emplace_back(ErrorType::Minor, loc, s_loc, msg, Token{ TokenType{}, "", Location{} }); 149 | has_minor_errors_ = true; 150 | } 151 | 152 | void night::error::create_minor_error( 153 | std::string const& message, 154 | Token const& token, 155 | std::source_location const& s_loc) noexcept 156 | { 157 | error::get().errors.emplace_back(ErrorType::Minor, token.loc, s_loc, message, token); 158 | has_minor_errors_ = true; 159 | } 160 | 161 | night::error const& night::error::create_fatal_error( 162 | std::string const& msg, 163 | Location const& loc, 164 | std::source_location const& s_loc) noexcept 165 | { 166 | error::get().errors.emplace_back(ErrorType::FatalCompile, loc, s_loc, msg); 167 | return error::get(); 168 | } 169 | 170 | night::error const& night::error::create_runtime_error( 171 | std::string const& msg, 172 | std::source_location const& s_loc) noexcept 173 | { 174 | error::get().errors.emplace_back(ErrorType::FatalRuntime, Location{}, s_loc, msg); 175 | return error::get(); 176 | } 177 | 178 | bool night::error::has_minor_errors() const 179 | { 180 | return has_minor_errors_; 181 | } -------------------------------------------------------------------------------- /code/src/common/token.cpp: -------------------------------------------------------------------------------- 1 | #include "common/token.hpp" 2 | #include "common/debug.hpp" 3 | 4 | #include 5 | 6 | std::string night::to_str(TokenType type) 7 | { 8 | switch (type) 9 | { 10 | case TokenType::ASSIGN: return "assignment"; 11 | case TokenType::ASSIGN_OPERATOR: return "assignment operator"; 12 | case TokenType::OPEN_BRACKET: return "open bracket"; 13 | case TokenType::CLOSE_BRACKET: return "close bracket"; 14 | case TokenType::OPEN_SQUARE: return "open square"; 15 | case TokenType::CLOSE_SQUARE: return "close square"; 16 | case TokenType::OPEN_CURLY: return "open curly"; 17 | case TokenType::CLOSE_CURLY: return "close curly"; 18 | case TokenType::COLON: return "colon"; 19 | case TokenType::SEMICOLON: return "semicolon"; 20 | case TokenType::COMMA: return "comma"; 21 | case TokenType::UNARY_OPERATOR: return "unary operator"; 22 | case TokenType::BINARY_OPERATOR: return "binary operator"; 23 | case TokenType::BOOL_LIT: return "boolean"; 24 | case TokenType::CHAR_LIT: return "character"; 25 | case TokenType::INT_LIT: return "integer"; 26 | case TokenType::FLOAT_LIT: return "float"; 27 | case TokenType::STRING_LIT: return "string"; 28 | case TokenType::VARIABLE: return "variable"; 29 | case TokenType::TYPE: return "type"; 30 | case TokenType::IF: return "if"; 31 | case TokenType::ELIF: return "elif"; 32 | case TokenType::ELSE: return "else"; 33 | case TokenType::FOR:return "for"; 34 | case TokenType::WHILE: return "while"; 35 | case TokenType::DEF: return "def"; 36 | case TokenType::VOID: return "void"; 37 | case TokenType::RETURN: return "return"; 38 | case TokenType::END_OF_FILE: return "end of file"; 39 | default: throw debug::unhandled_case((int)type); 40 | } 41 | } -------------------------------------------------------------------------------- /code/src/common/type.cpp: -------------------------------------------------------------------------------- 1 | #include "common/type.hpp" 2 | #include "common/debug.hpp" 3 | 4 | #include 5 | 6 | Type::Type(std::string const& _prim, int _dim) 7 | : dim(_dim) 8 | { 9 | if (_prim == "bool") prim = Primitive::BOOL; 10 | else if (_prim == "char") prim = Primitive::CHAR; 11 | else if (_prim == "int") prim = Primitive::INT; 12 | else if (_prim == "float") prim = Primitive::FLOAT; 13 | else throw debug::unhandled_case(_prim); 14 | } 15 | 16 | Type::Type(Primitive _prim, int _dim) 17 | : prim(_prim), dim(_dim) {} 18 | 19 | Type::Type(Type const& _other) 20 | : prim(_other.prim), dim(_other.dim) {} 21 | 22 | bool Type::operator==(Primitive _prim) const 23 | { 24 | return prim == _prim && dim == 0; 25 | } 26 | 27 | bool Type::operator==(Type const& _type) const 28 | { 29 | return prim == _type.prim && 30 | dim == _type.dim; 31 | } 32 | 33 | bool Type::is_prim() const { return !dim; } 34 | 35 | bool Type::is_arr() const { return dim; } 36 | 37 | bool Type::is_str() const { return prim == Primitive::CHAR && dim == 1; } 38 | 39 | std::string night::to_str(Type const& type) 40 | { 41 | if (type.prim == Type::CHAR && type.dim == 1) 42 | return "string"; 43 | 44 | std::string is_arr_str = (type.is_arr() ? " array" : ""); 45 | switch (type.prim) 46 | { 47 | case Type::BOOL: return "boolean" + is_arr_str; 48 | case Type::CHAR: return "character" + is_arr_str; 49 | case Type::INT: return "integer" + is_arr_str; 50 | case Type::FLOAT: return "float" + is_arr_str; 51 | default: throw debug::unhandled_case(type.prim); 52 | } 53 | } -------------------------------------------------------------------------------- /code/src/interpreter/interpreter.cpp: -------------------------------------------------------------------------------- 1 | #include "interpreter/interpreter.hpp" 2 | #include "interpreter/interpreter_scope.hpp" 3 | #include "common/error.hpp" 4 | #include "common/debug.hpp" 5 | #include "language.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include // PRId64 18 | 19 | std::optional interpret_bytecodes(InterpreterScope& scope, bytecodes_t const& codes, bool is_global, char* buf) 20 | { 21 | if (!InterpreterScope::global_scope) 22 | InterpreterScope::global_scope = &scope; 23 | 24 | // Disable stdout buffering 25 | setbuf(stdout, NULL); 26 | 27 | std::stack s; 28 | 29 | // This freeze is for while loop bytecode. 30 | // The last JUMP in While loop bytecode jumps to before the start of the vector. 31 | // But you can not have an iterator point to before the start of a vector. So 32 | // the iterator will jump to the first element, and stay there instead of being 33 | // incremented by the for loop. 34 | bool freeze = false; 35 | 36 | for (auto it = std::begin(codes); it != std::end(codes); ++it) 37 | { 38 | if (freeze) 39 | { 40 | --it; 41 | freeze = false; 42 | } 43 | 44 | switch (*it) 45 | { 46 | case ByteType_sINT1: s.emplace(interpret_int(it, 1)); break; 47 | case ByteType_sINT2: s.emplace(interpret_int(it, 2)); break; 48 | case ByteType_sINT4: s.emplace(interpret_int(it, 4)); break; 49 | case ByteType_sINT8: s.emplace(interpret_int(it, 8)); break; 50 | 51 | case ByteType_uINT1: s.emplace(interpret_int(it, 1)); break; 52 | case ByteType_uINT2: s.emplace(interpret_int(it, 2)); break; 53 | case ByteType_uINT4: s.emplace(interpret_int(it, 4)); break; 54 | case ByteType_uINT8: s.emplace(interpret_int(it, 8)); break; 55 | 56 | case ByteType_FLT4: s.emplace(interpret_flt(it, 4)); break; 57 | case ByteType_FLT8: s.emplace(interpret_flt(it, 8)); break; 58 | 59 | case BytecodeType_NEGATIVE_I: 60 | s.emplace(-pop(s).as.i); 61 | break; 62 | case BytecodeType_NEGATIVE_F: 63 | s.emplace(-pop(s).as.d); 64 | break; 65 | 66 | case BytecodeType_NOT_I: 67 | s.emplace((int64_t)!pop(s).as.i); 68 | break; 69 | case BytecodeType_NOT_F: 70 | s.emplace((int64_t)!pop(s).as.d); 71 | break; 72 | 73 | case BytecodeType_ADD_I: 74 | s.emplace(pop(s).as.i + pop(s).as.i); 75 | break; 76 | case BytecodeType_ADD_F: 77 | s.emplace(pop(s).as.d + pop(s).as.d); 78 | break; 79 | case BytecodeType_ADD_S: { 80 | auto s1 = pop(s).as.s; 81 | auto s2 = pop(s).as.s; 82 | 83 | size_t s1_len = strlen(s1); 84 | size_t s2_len = strlen(s2); 85 | size_t len = s1_len + s2_len + 1; 86 | char* result = (char*)malloc(sizeof(char) * len); 87 | if (!result) 88 | exit(1); 89 | 90 | strncpy(result, s2, len); 91 | result[len - 1] = '\0'; 92 | strncat(result, s1, len - s2_len - 1); 93 | 94 | s.emplace(result); 95 | break; 96 | } 97 | 98 | case BytecodeType_SUB_I: { 99 | auto s1 = pop(s).as.i; 100 | auto s2 = pop(s).as.i; 101 | s.emplace(-s1 + s2); 102 | break; 103 | } 104 | case BytecodeType_SUB_F: { 105 | auto s1 = pop(s).as.d; 106 | auto s2 = pop(s).as.d; 107 | s.emplace(-s1 + s2); 108 | break; 109 | } 110 | 111 | case BytecodeType_MULT_I: { 112 | auto s1 = pop(s).as.i; 113 | auto s2 = pop(s).as.i; 114 | s.emplace(s1 * s2); 115 | break; 116 | } 117 | case BytecodeType_MULT_F: { 118 | auto s1 = pop(s).as.d; 119 | auto s2 = pop(s).as.d; 120 | s.emplace(s1 * s2); 121 | break; 122 | } 123 | 124 | case BytecodeType_DIV_I: { 125 | auto s2 = pop(s); 126 | s.emplace(pop(s).as.i / s2.as.i); 127 | break; 128 | } 129 | case BytecodeType_DIV_F: { 130 | auto s2 = pop(s); 131 | s.emplace(pop(s).as.d / s2.as.d); 132 | break; 133 | } 134 | case ByteType_MOD: { 135 | auto s2 = pop(s); 136 | s.emplace(pop(s).as.i % s2.as.i); 137 | break; 138 | } 139 | 140 | // stack values are in opposite order, so we switch signs to account for that 141 | case BytecodeType_LESSER_I: { 142 | auto s1 = pop(s).as.i; 143 | auto s2 = pop(s).as.i; 144 | s.emplace(int64_t(s1 > s2)); 145 | break; 146 | } 147 | case BytecodeType_LESSER_F: { 148 | auto s1 = pop(s).as.d; 149 | auto s2 = pop(s).as.d; 150 | s.emplace(int64_t(s1 > s2)); 151 | break; 152 | } 153 | case BytecodeType_LESSER_S: { 154 | auto s1 = pop(s).as.s; 155 | auto s2 = pop(s).as.s; 156 | s.emplace(int64_t(strcmp(s1, s2) > 0)); 157 | break; 158 | } 159 | 160 | case BytecodeType_GREATER_I: { 161 | auto s1 = pop(s).as.i; 162 | auto s2 = pop(s).as.i; 163 | s.emplace(int64_t(s1 < s2)); 164 | break; 165 | } 166 | case BytecodeType_GREATER_F: { 167 | auto s1 = pop(s).as.d; 168 | auto s2 = pop(s).as.d; 169 | s.emplace(int64_t(s1 < s2)); 170 | break; 171 | } 172 | case BytecodeType_GREATER_S: { 173 | auto s1 = pop(s).as.s; 174 | auto s2 = pop(s).as.s; 175 | s.emplace(int64_t(strcmp(s1, s2) < 0)); 176 | break; 177 | } 178 | 179 | case BytecodeType_LESSER_EQUALS_I: { 180 | auto s1 = pop(s).as.i; 181 | auto s2 = pop(s).as.i; 182 | s.emplace((int64_t)(s1 >= s2)); 183 | break; 184 | } 185 | case BytecodeType_LESSER_EQUALS_F: { 186 | auto s1 = pop(s).as.d; 187 | auto s2 = pop(s).as.d; 188 | s.emplace((int64_t)(s1 >= s2)); 189 | break; 190 | } 191 | case BytecodeType_LESSER_EQUALS_S: { 192 | auto s1 = pop(s).as.s; 193 | auto s2 = pop(s).as.s; 194 | s.emplace((int64_t)(strcmp(s1, s2) >= 0)); 195 | break; 196 | } 197 | 198 | case BytecodeType_GREATER_EQUALS_I: { 199 | auto s1 = pop(s).as.i; 200 | auto s2 = pop(s).as.i; 201 | s.emplace((int64_t)(s1 <= s2)); 202 | break; 203 | } 204 | case BytecodeType_GREATER_EQUALS_F: { 205 | auto s1 = pop(s).as.d; 206 | auto s2 = pop(s).as.d; 207 | s.emplace((int64_t)(s1 <= s2)); 208 | break; 209 | } 210 | case BytecodeType_GREATER_EQUALS_S: { 211 | auto s1 = pop(s).as.s; 212 | auto s2 = pop(s).as.s; 213 | s.emplace((int64_t)(strcmp(s1, s2) <= 0)); 214 | break; 215 | } 216 | 217 | case BytecodeType_EQUALS_I: { 218 | auto s1 = pop(s).as.i; 219 | auto s2 = pop(s).as.i; 220 | s.emplace(int64_t(s1 == s2)); 221 | break; 222 | } 223 | case BytecodeType_EQUALS_F: { 224 | auto s1 = pop(s).as.d; 225 | auto s2 = pop(s).as.d; 226 | s.emplace(int64_t(s1 == s2)); 227 | break; 228 | } 229 | case BytecodeType_EQUALS_S: { 230 | auto s1 = pop(s).as.s; 231 | auto s2 = pop(s).as.s; 232 | s.emplace(int64_t(!strcmp(s1, s2))); 233 | break; 234 | } 235 | 236 | case BytecodeType_NOT_EQUALS_I: { 237 | auto s1 = pop(s).as.i; 238 | auto s2 = pop(s).as.i; 239 | s.emplace(int64_t(s1 != s2)); 240 | break; 241 | } 242 | case BytecodeType_NOT_EQUALS_F: { 243 | auto s1 = pop(s).as.d; 244 | auto s2 = pop(s).as.d; 245 | s.emplace(int64_t(s1 != s2)); 246 | break; 247 | } 248 | case BytecodeType_NOT_EQUALS_S: { 249 | auto s1 = pop(s).as.s; 250 | auto s2 = pop(s).as.s; 251 | s.emplace((int64_t)strcmp(s1, s2)); 252 | break; 253 | } 254 | 255 | case BytecodeType_AND: { 256 | auto s1 = pop(s).as.i; 257 | auto s2 = pop(s).as.i; 258 | s.emplace(int64_t(s1 && s2)); 259 | break; 260 | } 261 | case BytecodeType_OR: { 262 | auto s1 = pop(s).as.i; 263 | auto s2 = pop(s).as.i; 264 | s.emplace(int64_t(s1 || s2)); 265 | break; 266 | } 267 | 268 | case BytecodeType_INDEX_S: push_subscript(s, true); break; 269 | case BytecodeType_INDEX_A: push_subscript(s, false); break; 270 | 271 | case ByteType_LOAD: { 272 | night::id_t id = pop(s).as.ui; 273 | s.emplace(scope.get_variable(id)); 274 | 275 | break; 276 | } 277 | 278 | case ByteType_STORE: { 279 | night::id_t id = pop(s).as.ui; 280 | scope.set_variable(id, pop(s)); 281 | 282 | break; 283 | } 284 | 285 | case BytecodeType_LOAD_ELEM: { 286 | uint64_t id = pop(s).as.ui; 287 | uint64_t num = pop(s).as.ui; 288 | intpr::Value* val = &scope.get_variable(id); 289 | while (num--) 290 | { 291 | auto i = pop(s).as.i; 292 | val = &val->as.a.data[i]; 293 | } 294 | s.push(*val); 295 | break; 296 | } 297 | 298 | case BytecodeType_ALLOCATE_STR: push_str(s); break; 299 | case BytecodeType_ALLOCATE_ARR: push_arr(s); break; 300 | case BytecodeType_ALLOCATE_ARR_AND_FILL: push_arr_and_fill(s); break; 301 | 302 | case BytecodeType_STORE_INDEX_A: { 303 | auto id = pop(s).as.i; 304 | auto expr = pop(s); 305 | intpr::Value* val = &scope.get_variable(id); 306 | while (!s.empty()) 307 | { 308 | auto i = pop(s).as.i; 309 | val = &val->as.a.data[i]; 310 | } 311 | *val = expr; 312 | break; 313 | } 314 | 315 | case BytecodeType_STORE_INDEX_S: { 316 | auto id = pop(s).as.i; 317 | auto expr = pop(s); 318 | scope.get_variable(id).as.s[pop(s).as.i] = (char)expr.as.i; 319 | break; 320 | } 321 | 322 | case BytecodeType_JUMP_IF_FALSE: { 323 | auto offset = pop(s).as.i; 324 | if (!pop(s).as.i) 325 | std::advance(it, offset); 326 | break; 327 | } 328 | 329 | case BytecodeType_JUMP: 330 | std::advance(it, pop(s).as.i); 331 | break; 332 | case ByteType_JUMP_N: 333 | std::advance(it, -pop(s).as.i); 334 | freeze = true; 335 | break; 336 | 337 | case BytecodeType_RETURN: { 338 | if (s.empty()) 339 | return std::nullopt; 340 | 341 | return pop(s); 342 | } 343 | 344 | case BytecodeType_CALL: { 345 | night::id_t id = pop(s).as.i; 346 | 347 | switch (id) 348 | { 349 | case PredefinedFunctions::PRINT_BOOL: 350 | if (!buf) 351 | printf(pop(s).as.i ? "true" : "false"); 352 | else 353 | sprintf(buf + strlen(buf), pop(s).as.i ? "true" : "false"); 354 | break; 355 | case PredefinedFunctions::PRINT_CHAR: 356 | if (!buf) 357 | printf("%c", (char)pop(s).as.i); 358 | else 359 | sprintf(buf + strlen(buf), "%c", (char)pop(s).as.i); 360 | break; 361 | case PredefinedFunctions::PRINT_INT: 362 | if (!buf) 363 | printf("%lld", pop(s).as.i); 364 | else 365 | sprintf(buf + strlen(buf), "%lld", pop(s).as.i); 366 | break; 367 | case PredefinedFunctions::PRINT_FLOAT: 368 | if (!buf) 369 | printf("%.17gf", pop(s).as.d); 370 | else 371 | sprintf(buf + strlen(buf), "%.17gf", pop(s).as.d); 372 | break; 373 | case PredefinedFunctions::PRINT_STR: 374 | if (!buf) 375 | printf("%s", pop(s).as.s); 376 | else 377 | sprintf(buf + strlen(buf), "%s", pop(s).as.s); 378 | break; 379 | case PredefinedFunctions::INPUT: 380 | s.emplace(interpret_predefined_input()); 381 | break; 382 | case PredefinedFunctions::INT_TO_CHAR: 383 | //s.emplace((int64_t)int_to_char(pop(s).as.i)); 384 | break; 385 | case PredefinedFunctions::STR_TO_CHAR: { 386 | char* str = pop(s).as.s; 387 | if (strlen(str) != 1) 388 | throw night::error::get().create_runtime_error("Could not convert string to char."); 389 | 390 | s.emplace((int64_t)str[0]); 391 | break; 392 | } 393 | case PredefinedFunctions::BOOL_TO_INT: 394 | //s.emplace((int64_t)bool_to_int((bool)pop(s).as.i)); 395 | break; 396 | case PredefinedFunctions::CHAR_TO_INT: 397 | //s.emplace((int64_t)char_to_int((char)pop(s).as.i)); 398 | break; 399 | case PredefinedFunctions::FLOAT_TO_INT: 400 | s.emplace((int64_t)float_to_int(pop(s).as.d)); 401 | break; 402 | case PredefinedFunctions::STR_TO_INT: 403 | s.emplace((int64_t)str_to_int(pop(s).as.s)); 404 | break; 405 | case PredefinedFunctions::BOOL_TO_FLOAT: 406 | s.emplace(bool_to_float((bool)pop(s).as.i)); 407 | break; 408 | case PredefinedFunctions::CHAR_TO_FLOAT: 409 | s.emplace(char_to_float((char)pop(s).as.i)); 410 | break; 411 | case PredefinedFunctions::INT_TO_FLOAT: 412 | s.emplace(int_to_float(pop(s).as.i)); 413 | break; 414 | case PredefinedFunctions::STR_TO_FLOAT: 415 | s.emplace(str_to_float(pop(s).as.s)); 416 | break; 417 | case PredefinedFunctions::CHAR_TO_STR: 418 | s.emplace(char_to_str((char)pop(s).as.i)); 419 | break; 420 | case PredefinedFunctions::INT_TO_STR: 421 | s.emplace(int_to_str(pop(s).as.i)); 422 | break; 423 | case PredefinedFunctions::FLOAT_TO_STR: 424 | s.emplace(float_to_str((float)pop(s).as.d)); 425 | break; 426 | case PredefinedFunctions::LEN: 427 | s.emplace((int64_t)len(pop(s).as.s)); 428 | break; 429 | default: { 430 | // Functions' only allowed parent scope is the global scope 431 | InterpreterScope func_scope(InterpreterScope::global_scope); 432 | 433 | for (int i = scope.funcs[id].param_ids.size() - 1; i >= 0; --i) 434 | func_scope.set_variable(InterpreterScope::funcs[id].param_ids[i], pop(s)); 435 | 436 | auto rtn_value = interpret_bytecodes(func_scope, InterpreterScope::funcs[id].codes, false, buf); 437 | if (rtn_value.has_value()) 438 | s.push(*rtn_value); 439 | 440 | break; 441 | } 442 | } 443 | 444 | break; 445 | } 446 | 447 | default: 448 | throw debug::unhandled_case(*it); 449 | } 450 | } 451 | 452 | return std::nullopt; 453 | } 454 | 455 | double interpret_flt(bytecodes_t::const_iterator& it, unsigned short size) 456 | { 457 | assert(size == 4 || size == 8); 458 | 459 | union { 460 | uint8_t bytes[sizeof(double)]; 461 | double d; 462 | float f; 463 | }; 464 | 465 | for (unsigned short i = 0; i < size; ++i) 466 | bytes[i] = *(++it); 467 | 468 | return size == 4 ? f : d; 469 | } 470 | 471 | char* interpret_predefined_input() 472 | { 473 | int size = 32; 474 | char* buf = (char*)malloc(sizeof(char) * size); 475 | 476 | if (!buf) 477 | exit(1); 478 | 479 | int c; 480 | int len = 0; 481 | while ((c = getchar()) != '\n' && c != EOF) 482 | { 483 | if (len == size - 1) 484 | { 485 | size *= 1.5; 486 | char* new_buf = (char*)realloc(buf, sizeof(char) * size); 487 | 488 | if (!new_buf) 489 | { 490 | free(buf); 491 | exit(1); 492 | } 493 | 494 | buf = new_buf; 495 | } 496 | 497 | buf[len] = c; 498 | ++len; 499 | } 500 | 501 | buf[len] = '\0'; 502 | return buf; 503 | } 504 | 505 | void push_str(std::stack& s) 506 | { 507 | uint64_t size = pop(s).as.ui; 508 | char* arr = (char*)malloc((size + 1) * sizeof(char)); 509 | 510 | for (uint64_t i = size; i > 0; --i) 511 | arr[i - 1] = (char)pop(s).as.i; 512 | 513 | arr[size] = '\0'; 514 | 515 | s.push(arr); 516 | } 517 | 518 | void push_arr(std::stack& s) 519 | { 520 | uint64_t size = pop(s).as.ui; 521 | intpr::Value arr; 522 | arr.as.a.size = size; 523 | arr.as.a.data = new intpr::Value[size]; 524 | 525 | for (uint64_t i = size; i > 0; --i) 526 | arr.as.a.data[i - 1] = pop(s); 527 | 528 | s.push(arr); 529 | } 530 | 531 | void push_arr_and_fill(std::stack& s) 532 | { 533 | uint64_t dimensions = pop(s).as.ui; 534 | std::vector sizes(dimensions); 535 | 536 | for (uint64_t i = dimensions; i > 0; --i) 537 | sizes[i - 1] = (int)pop(s).as.i; 538 | 539 | intpr::Value arr; 540 | fill_arr(arr, s, sizes, 0); 541 | 542 | s.push(arr); 543 | } 544 | 545 | void fill_arr(intpr::Value& arr, std::stack& s, std::vector const& dimensions, int current_dimension) 546 | { 547 | if (current_dimension < dimensions.size()) 548 | { 549 | arr.as.a.size = dimensions[current_dimension]; 550 | arr.as.a.data = new intpr::Value[dimensions[current_dimension]]; 551 | 552 | for (int i = 0; i < dimensions[current_dimension]; ++i) 553 | { 554 | intpr::Value element; 555 | fill_arr(element, s, dimensions, current_dimension + 1); 556 | 557 | arr.as.a.data[i] = element; 558 | } 559 | } 560 | else 561 | { 562 | arr.as.i = 0; 563 | arr.as.ui = 0; 564 | arr.as.d = 0; 565 | } 566 | } 567 | 568 | void push_subscript(std::stack& s, bool is_string) 569 | { 570 | auto container = pop(s); 571 | auto index = pop(s); 572 | 573 | if (is_string) 574 | s.emplace((int64_t)container.as.s[index.as.i]); 575 | else 576 | s.emplace(container.as.a.data[index.as.i]); 577 | } 578 | 579 | void push_string_input(std::stack& s) 580 | { 581 | int size = 32; 582 | char* buf = (char*)malloc(sizeof(char) * size); 583 | 584 | if (!buf) 585 | exit(1); 586 | 587 | int c; 588 | int len = 0; 589 | while ((c = getchar()) != '\n' && c != EOF) 590 | { 591 | if (len == size - 1) 592 | { 593 | size = (int)(size * 1.5); 594 | char* new_buf = (char*)realloc(buf, sizeof(char) * size); 595 | 596 | if (!new_buf) 597 | { 598 | free(buf); 599 | exit(1); 600 | } 601 | 602 | buf = new_buf; 603 | } 604 | 605 | buf[len] = c; 606 | ++len; 607 | } 608 | 609 | buf[len] = '\0'; 610 | s.emplace(buf); 611 | } 612 | 613 | intpr::Value pop(std::stack& s) 614 | { 615 | assert(!s.empty()); 616 | 617 | auto val = s.top(); 618 | s.pop(); 619 | 620 | return val; 621 | } 622 | -------------------------------------------------------------------------------- /code/src/interpreter/interpreter_scope.cpp: -------------------------------------------------------------------------------- 1 | #include "interpreter/interpreter_scope.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | InterpreterScope* InterpreterScope::global_scope = nullptr; 9 | func_container InterpreterScope::funcs = {}; 10 | 11 | intpr::Value::Value(int64_t _i) { as.i = _i; } 12 | 13 | intpr::Value::Value(uint64_t _ui) { as.ui = _ui; } 14 | 15 | intpr::Value::Value(double _d) { as.d = _d; } 16 | 17 | intpr::Value::Value(char* _s) 18 | { 19 | assert(_s); 20 | 21 | size_t len = strlen(_s); 22 | 23 | as.s = (char*)malloc((len + 1) * sizeof(char)); 24 | if (!as.s) 25 | exit(1); 26 | 27 | strncpy(as.s, _s, len); 28 | as.s[len] = '\0'; 29 | } 30 | 31 | intpr::Value::Value(Array _a) 32 | { 33 | as.a = _a; 34 | } 35 | 36 | intpr::Value::Value(Value const& _v) 37 | { 38 | as = _v.as; 39 | } 40 | 41 | intpr::Value::~Value() 42 | { 43 | 44 | }; 45 | 46 | InterpreterScope::InterpreterScope() 47 | : parent(nullptr) {} 48 | 49 | InterpreterScope::InterpreterScope(InterpreterScope* _parent) 50 | : parent(_parent) {} 51 | 52 | intpr::Value& InterpreterScope::get_variable(night::id_t id) 53 | { 54 | if (vars.contains(id)) 55 | return vars[id]; 56 | 57 | assert(parent); 58 | return parent->get_variable(id); 59 | } 60 | 61 | void InterpreterScope::set_variable(night::id_t id, intpr::Value const& val) 62 | { 63 | if (vars.contains(id)) 64 | { 65 | vars[id] = val; 66 | return; 67 | } 68 | 69 | if (!parent || !parent->has_variable(id)) 70 | { 71 | vars[id] = val; 72 | return; 73 | } 74 | 75 | assert(parent); 76 | parent->set_variable(id, val); 77 | } 78 | 79 | bool InterpreterScope::has_variable(night::id_t id) 80 | { 81 | if (vars.contains(id)) 82 | return true; 83 | 84 | if (!parent) 85 | return false; 86 | 87 | return parent->has_variable(id); 88 | } 89 | -------------------------------------------------------------------------------- /code/src/language.cpp: -------------------------------------------------------------------------------- 1 | #include "language.hpp" 2 | #include "common/error.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int bool_to_int(bool b) 12 | { 13 | return (int)b; 14 | } 15 | 16 | int char_to_int(char c) 17 | { 18 | return (int)c; 19 | } 20 | 21 | int float_to_int(double d) 22 | { 23 | return (int)d; 24 | } 25 | 26 | long long str_to_int(char* s) 27 | { 28 | assert(s); 29 | 30 | errno = 0; 31 | char* temp; 32 | long long val = strtoll(s, &temp, 10); 33 | 34 | if (temp == s || *temp != '\0' || 35 | ((val == LLONG_MIN || val == LLONG_MAX) && errno == ERANGE)) 36 | fprintf(stderr, "Could not convert '%s' to long long and leftover string is: '%s'\n", s, temp); 37 | 38 | return val; 39 | } 40 | 41 | float bool_to_float(bool b) 42 | { 43 | return (float)b; 44 | } 45 | 46 | float char_to_float(char c) 47 | { 48 | return (float)c; 49 | } 50 | 51 | float int_to_float(int i) 52 | { 53 | return (float)i; 54 | } 55 | 56 | float str_to_float(char* s) 57 | { 58 | assert(s); 59 | 60 | errno = 0; 61 | char* temp; 62 | float val = strtod(s, &temp); 63 | 64 | if (temp == s || *temp != '\0' || 65 | ((val == FLT_MIN || val == FLT_MAX) && errno == ERANGE)) 66 | fprintf(stderr, "Could not convert '%s' to float and leftover string is: '%s'\n", s, temp); 67 | 68 | return val; 69 | } 70 | 71 | char* char_to_str(char c) 72 | { 73 | char* s = (char*)malloc(2 * sizeof(char)); 74 | s[0] = c; 75 | s[1] = '\0'; 76 | 77 | return s; 78 | } 79 | 80 | char* int_to_str(int i) 81 | { 82 | char* s = (char*)malloc(32 * sizeof(char)); 83 | 84 | sprintf(s, "%d", i); 85 | return s; 86 | } 87 | 88 | char* float_to_str(float f) 89 | { 90 | int size = 65; 91 | char* s = (char*)malloc(size); 92 | 93 | sprintf(s, "%f", f); 94 | return s; 95 | } 96 | 97 | int len(char* s) 98 | { 99 | assert(s); 100 | 101 | return strlen(s); 102 | } -------------------------------------------------------------------------------- /code/src/lexer/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer/lexer.hpp" 2 | #include "common/token.hpp" 3 | #include "common/error.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | Lexer::Lexer(std::string const& _file_name) 14 | : file(_file_name), loc({ _file_name, 1, 0 }), prev_tok(std::nullopt) 15 | { 16 | if (!file.is_open()) 17 | throw night::error::get().create_fatal_error("file '" + loc.file + "' could not be found/opened", loc); 18 | 19 | std::getline(file, file_line); 20 | eat(); 21 | } 22 | 23 | Lexer::~Lexer() 24 | { 25 | file.close(); 26 | } 27 | 28 | Token const& Lexer::eat() 29 | { 30 | assert(curr().type != TokenType::END_OF_FILE); 31 | 32 | if (prev_tok.has_value()) 33 | { 34 | prev_tok.reset(); 35 | return curr_tok; 36 | } 37 | 38 | while (loc.col < file_line.size() && std::isspace(file_line[loc.col])) 39 | ++loc.col; 40 | 41 | if (loc.col == file_line.size() || file_line[loc.col] == '#') 42 | return curr_tok = eat_new_line(); 43 | 44 | 45 | if (std::isdigit(file_line[loc.col])) 46 | return curr_tok = eat_number(); 47 | 48 | if (file_line[loc.col] == '"') 49 | return curr_tok = eat_string(); 50 | 51 | if (file_line[loc.col] == '\'') 52 | return curr_tok = eat_character(); 53 | 54 | if (std::isalpha(file_line[loc.col]) || file_line[loc.col] == '_') 55 | return curr_tok = eat_keyword(); 56 | 57 | return curr_tok = eat_symbol(); 58 | } 59 | 60 | Token Lexer::peek() 61 | { 62 | if (prev_tok.has_value()) 63 | return curr_tok; 64 | 65 | auto tmp_tok = curr_tok; 66 | auto eat_tok = eat(); 67 | prev_tok = tmp_tok; 68 | 69 | return eat_tok; 70 | } 71 | 72 | Token const& Lexer::curr() const 73 | { 74 | if (prev_tok.has_value()) 75 | return *prev_tok; 76 | 77 | return curr_tok; 78 | } 79 | 80 | Token const& Lexer::expect(TokenType type, std::string const& err, std::source_location const& s_loc) 81 | { 82 | if (!prev_tok.has_value()) 83 | eat(); 84 | 85 | if (curr_tok.type != type) 86 | throw night::error::get().create_fatal_error("found '" + curr().str + "', expected " + night::to_str(type) + " " + err, loc, s_loc); 87 | 88 | prev_tok.reset(); 89 | return curr_tok; 90 | } 91 | 92 | Token const& Lexer::curr_is(TokenType type, std::string const& err_msg, std::source_location const& s_loc) 93 | { 94 | if (curr().type != type) 95 | throw night::error::get().create_fatal_error( 96 | err_msg.empty() 97 | ? "found '" + night::to_str(curr_tok.type) + "', expected '" + night::to_str(type) + "'" 98 | : err_msg, 99 | loc, s_loc); 100 | 101 | return curr(); 102 | } 103 | 104 | void Lexer::scan_code(std::string const& code) 105 | { 106 | loc.col = 0; 107 | file_line = code; 108 | eat(); 109 | } 110 | 111 | Token Lexer::eat_string() 112 | { 113 | ++loc.col; 114 | 115 | std::string str; 116 | 117 | while (true) 118 | { 119 | if (loc.col == file_line.size() && !new_line()) 120 | std::cout << "expected closing quotes for string '" + str + "'"; 121 | 122 | if (file_line[loc.col] == '"') 123 | break; 124 | 125 | bool match = false; 126 | char chr; 127 | 128 | if (loc.col < file_line.length() - 1 && file_line[loc.col] == '\\') 129 | { 130 | match = true; 131 | 132 | switch (file_line[loc.col + 1]) 133 | { 134 | case '\\': chr = '\\'; break; 135 | case 'n': chr = '\n'; break; 136 | case 't': chr = '\t'; break; 137 | case '"': chr = '\"'; break; 138 | case '0': chr = '\0'; break; 139 | default: 140 | match = false; 141 | } 142 | } 143 | 144 | 145 | if (!match) 146 | { 147 | str += file_line[loc.col]; 148 | ++loc.col; 149 | } 150 | else 151 | { 152 | str += chr; 153 | loc.col += 2; 154 | } 155 | } 156 | 157 | ++loc.col; 158 | return { TokenType::STRING_LIT, str, loc }; 159 | } 160 | 161 | Token Lexer::eat_character() 162 | { 163 | ++loc.col; 164 | 165 | if (loc.col == file_line.length()) 166 | throw night::error::get().create_fatal_error("", loc); 167 | 168 | if (file_line[loc.col] == '\'') 169 | throw night::error::get().create_fatal_error("character can not not be empty", loc); 170 | 171 | char chr; 172 | 173 | if (file_line[loc.col] == '\\') 174 | { 175 | ++loc.col; 176 | switch (file_line[loc.col]) 177 | { 178 | case '\\': chr = '\\'; break; 179 | case 'n': chr = '\n'; break; 180 | case 't': chr = '\t'; break; 181 | case '"': chr = '\"'; break; 182 | case '0': chr = '\0'; break; 183 | default: 184 | throw night::error::get().create_fatal_error("unknown character '\\'" + file_line[loc.col], loc); 185 | } 186 | } 187 | else 188 | { 189 | chr = file_line[loc.col]; 190 | } 191 | 192 | if (file_line[++loc.col] != '\'') 193 | throw night::error::get().create_fatal_error(std::string() + "found '" + file_line[loc.col] + "', expected closing quote at the end of character", loc); 194 | 195 | ++loc.col; 196 | return { TokenType::CHAR_LIT, std::string(1, chr), loc }; 197 | } 198 | 199 | Token Lexer::eat_keyword() 200 | { 201 | static std::unordered_map const keywords{ 202 | { "true", TokenType::BOOL_LIT }, 203 | { "false", TokenType::BOOL_LIT }, 204 | { "char", TokenType::TYPE }, 205 | { "bool", TokenType::TYPE }, 206 | { "int", TokenType::TYPE }, 207 | { "float", TokenType::TYPE }, 208 | { "if", TokenType::IF }, 209 | { "elif", TokenType::ELIF }, 210 | { "else", TokenType::ELSE }, 211 | { "for", TokenType::FOR }, 212 | { "while", TokenType::WHILE }, 213 | { "def", TokenType::DEF }, 214 | { "void", TokenType::VOID }, 215 | { "return", TokenType::RETURN } 216 | }; 217 | 218 | std::string keyword; 219 | 220 | do { 221 | keyword += file_line[loc.col]; 222 | ++loc.col; 223 | } while (loc.col < file_line.length() && (std::isalpha(file_line[loc.col]) || std::isdigit(file_line[loc.col]) || file_line[loc.col] == '_')); 224 | 225 | if (auto it = keywords.find(keyword); it != keywords.end()) 226 | return Token{ it->second, keyword, loc }; 227 | else 228 | return Token{ TokenType::VARIABLE, keyword, loc }; 229 | } 230 | 231 | Token Lexer::eat_number() 232 | { 233 | std::string number; 234 | 235 | do { 236 | number += file_line[loc.col]; 237 | ++loc.col; 238 | } while (loc.col < file_line.length() && std::isdigit(file_line[loc.col])); 239 | 240 | // floats 241 | if (file_line[loc.col] == '.' && loc.col < file_line.length() - 1 && 242 | std::isdigit(file_line[loc.col + 1])) 243 | { 244 | number += "."; 245 | ++loc.col; 246 | 247 | do { 248 | number += file_line[loc.col]; 249 | ++loc.col; 250 | } while (loc.col < file_line.length() && std::isdigit(file_line[loc.col])); 251 | 252 | return { TokenType::FLOAT_LIT, number, loc }; 253 | } 254 | 255 | return { TokenType::INT_LIT, number, loc }; 256 | } 257 | 258 | Token Lexer::eat_symbol() 259 | { 260 | static std::unordered_map > > const symbols{ 261 | { '+', { { '=', TokenType::ASSIGN_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 262 | { '-', { { '=', TokenType::ASSIGN_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 263 | { '*', { { '=', TokenType::ASSIGN_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 264 | { '/', { { '=', TokenType::ASSIGN_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 265 | { '%', { { '=', TokenType::ASSIGN_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 266 | 267 | { '>', { { '=', TokenType::BINARY_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 268 | { '<', { { '=', TokenType::BINARY_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 269 | 270 | { '|', { { '|', TokenType::BINARY_OPERATOR } } }, 271 | { '&', { { '&', TokenType::BINARY_OPERATOR } } }, 272 | { '!', { { '=', TokenType::BINARY_OPERATOR }, { '\0', TokenType::UNARY_OPERATOR } } }, 273 | 274 | { '.', { { '.', TokenType::BINARY_OPERATOR }, { '\0', TokenType::BINARY_OPERATOR } } }, 275 | 276 | { '=', { { '=', TokenType::BINARY_OPERATOR }, { '\0', TokenType::ASSIGN } } }, 277 | 278 | { '(', { { '\0', TokenType::OPEN_BRACKET } } }, 279 | { ')', { { '\0', TokenType::CLOSE_BRACKET } } }, 280 | { '[', { { '\0', TokenType::OPEN_SQUARE } } }, 281 | { ']', { { '\0', TokenType::CLOSE_SQUARE } } }, 282 | { '{', { { '\0', TokenType::OPEN_CURLY } } }, 283 | { '}', { { '\0', TokenType::CLOSE_CURLY } } }, 284 | 285 | { ',', { { '\0', TokenType::COMMA } } }, 286 | { ':', { { '\0', TokenType::COLON } } }, 287 | { ';', { { '\0', TokenType::SEMICOLON } } } 288 | }; 289 | 290 | auto symbol = symbols.find(file_line[loc.col]); 291 | if (symbol == symbols.end()) 292 | throw night::error::get().create_fatal_error("unknown symbol '" + std::string(1, file_line[loc.col]) + "'", loc); 293 | 294 | for (auto& [c, tok_type] : symbol->second) 295 | { 296 | if (c == '\0') 297 | { 298 | ++loc.col; 299 | return { tok_type, std::string(1, file_line[loc.col - 1]), loc }; 300 | } 301 | 302 | if (loc.col < file_line.length() - 1 && file_line[loc.col + 1] == c) 303 | { 304 | loc.col += 2; 305 | return { tok_type, std::string(1, file_line[loc.col - 2]) + std::string(1, c), loc }; 306 | } 307 | } 308 | 309 | throw night::error::get().create_fatal_error("unknown symbol '" + file_line.substr(loc.col, 2) + "'", loc); 310 | } 311 | 312 | bool Lexer::new_line() 313 | { 314 | ++loc.line; 315 | loc.col = 0; 316 | 317 | return (bool)std::getline(file, file_line); 318 | } 319 | 320 | Token Lexer::eat_new_line() 321 | { 322 | if (!new_line()) 323 | return { TokenType::END_OF_FILE, "End of File", loc }; 324 | 325 | return eat(); 326 | } -------------------------------------------------------------------------------- /code/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "parse_args.hpp" 2 | #include "parser/statement_parser.hpp" 3 | #include "parser/code_gen.hpp" 4 | #include "interpreter/interpreter.hpp" 5 | #include "common/error.hpp" 6 | 7 | #include 8 | #include 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | auto main_file = parse_args(argc, argv); 13 | if (main_file.empty()) 14 | return 0; 15 | 16 | try { 17 | auto statements = parse_file(main_file); 18 | 19 | auto bytecodes = code_gen(statements); 20 | 21 | InterpreterScope scope; 22 | interpret_bytecodes(scope, bytecodes, true); 23 | 24 | if (night::error::get().warning_flag) 25 | night::error::get().what(true); 26 | 27 | return 0; 28 | } 29 | catch (night::error& e) { 30 | e.what(); 31 | 32 | if (night::error::get().warning_flag) 33 | night::error::get().what(true); 34 | 35 | return 1; 36 | } 37 | catch (std::exception const& e) { 38 | std::cout << "Oops! We've come across an unexpected error!\n\n" 39 | << e.what() << "\n" 40 | << "Please submit an issue on Github, https://github.com/alexapostolu/night\n"; 41 | 42 | return 1; 43 | } 44 | } -------------------------------------------------------------------------------- /code/src/parse_args.cpp: -------------------------------------------------------------------------------- 1 | #include "parse_args.hpp" 2 | 3 | #include "common/error.hpp" 4 | #include "version.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | std::string parse_args(int argc, char* argv[]) 11 | { 12 | std::string const more_info = "for more info, type:\n" 13 | " night --help\n\n"; 14 | 15 | std::string const help = "usage:\n" 16 | " night \n" 17 | " night \n" 18 | " night