├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── src ├── CMakeLists.txt ├── Main.cpp ├── Pch.hpp └── cero │ ├── driver │ ├── BuildCommand.cpp │ ├── BuildCommand.hpp │ ├── CompilerOptions.hpp │ ├── Run.cpp │ └── Run.hpp │ ├── report │ ├── CodeLocation.cpp │ ├── CodeLocation.hpp │ ├── ConsoleReporter.cpp │ ├── ConsoleReporter.hpp │ ├── ConsoleReporter.posix.cpp │ ├── ConsoleReporter.win.cpp │ ├── Message.cpp │ ├── Message.hpp │ ├── MessageArgs.cpp │ ├── MessageArgs.hpp │ ├── Reporter.cpp │ └── Reporter.hpp │ ├── syntax │ ├── Ast.cpp │ ├── Ast.hpp │ ├── AstCursor.cpp │ ├── AstCursor.hpp │ ├── AstNode.hpp │ ├── AstNodeKind.cpp │ ├── AstNodeKind.hpp │ ├── AstToString.cpp │ ├── AstToString.hpp │ ├── AstVisitor.hpp │ ├── Encoding.cpp │ ├── Encoding.hpp │ ├── Lexer.cpp │ ├── Lexer.hpp │ ├── Literal.cpp │ ├── Literal.hpp │ ├── Parser.cpp │ ├── Parser.hpp │ ├── Source.cpp │ ├── Source.hpp │ ├── SourceCursor.hpp │ ├── Token.cpp │ ├── Token.hpp │ ├── TokenCursor.hpp │ ├── TokenStream.cpp │ └── TokenStream.hpp │ └── util │ ├── Assert.cpp │ ├── Assert.hpp │ ├── FileMapping.hpp │ ├── FileMapping.posix.cpp │ ├── FileMapping.win.cpp │ ├── ImplStorage.hpp │ ├── ScopedAssign.hpp │ ├── SystemError.hpp │ ├── SystemError.posix.cpp │ ├── SystemError.win.cpp │ ├── Traits.hpp │ ├── UniqueImpl.hpp │ ├── WindowsApi.hpp │ ├── WindowsUtil.hpp │ └── WindowsUtil.win.cpp ├── tests ├── CMakeLists.txt ├── common │ ├── ExhaustiveReporter.cpp │ ├── ExhaustiveReporter.hpp │ ├── Test.cpp │ ├── Test.hpp │ └── TestRunner.cpp ├── driver │ └── BuildCommandTests.cpp └── syntax │ ├── AstCompare.cpp │ ├── AstCompare.hpp │ ├── AstStringResults.cpp │ ├── LexerErrors.cpp │ ├── LexerResults.cpp │ ├── ParserErrors.cpp │ ├── ParserResultsControlFlow.cpp │ ├── ParserResultsDefinitions.cpp │ ├── ParserResultsGenerics.cpp │ ├── ParserResultsOperators.cpp │ └── TokenStringResults.cpp ├── vcpkg-configuration.json └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -4 2 | AlignAfterOpenBracket: Align 3 | AlignArrayOfStructures: None 4 | AlignConsecutiveAssignments: 5 | Enabled: false 6 | AlignConsecutiveBitFields: 7 | Enabled: true 8 | AlignConsecutiveDeclarations: 9 | Enabled: false 10 | AlignConsecutiveMacros: 11 | Enabled: true 12 | AlignConsecutiveShortCaseStatements: 13 | Enabled: true 14 | AlignEscapedNewlines: Right 15 | AlignOperands: Align 16 | AlignTrailingComments: true 17 | AllowAllArgumentsOnNextLine: true 18 | AllowAllParametersOfDeclarationOnNextLine: false 19 | AllowShortBlocksOnASingleLine: Never 20 | AllowShortCaseLabelsOnASingleLine: true 21 | AllowShortEnumsOnASingleLine: false 22 | AllowShortFunctionsOnASingleLine: None 23 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 24 | AllowShortLambdasOnASingleLine: Empty 25 | AllowShortLoopsOnASingleLine: false 26 | AlwaysBreakAfterReturnType: None 27 | AlwaysBreakBeforeMultilineStrings: false 28 | AlwaysBreakTemplateDeclarations: Yes 29 | AttributeMacros: [ ] 30 | BinPackArguments: true 31 | BinPackParameters: false 32 | BitFieldColonSpacing: Both 33 | BraceWrapping: 34 | AfterCaseLabel: false 35 | AfterClass: false 36 | AfterControlStatement: Never 37 | AfterEnum: false 38 | AfterFunction: false 39 | AfterNamespace: false 40 | AfterObjCDeclaration: false 41 | AfterStruct: false 42 | AfterUnion: false 43 | AfterExternBlock: false 44 | BeforeCatch: false 45 | BeforeElse: false 46 | BeforeLambdaBody: false 47 | BeforeWhile: false 48 | IndentBraces: false 49 | SplitEmptyFunction: false 50 | SplitEmptyRecord: false 51 | SplitEmptyNamespace: false 52 | BreakAfterJavaFieldAnnotations: true 53 | BreakBeforeBinaryOperators: All 54 | BreakBeforeBraces: Custom 55 | BreakBeforeConceptDeclarations: true 56 | BreakBeforeTernaryOperators: true 57 | BreakConstructorInitializers: AfterColon 58 | BreakInheritanceList: AfterComma 59 | BreakStringLiterals: true 60 | ColumnLimit: 128 61 | CommentPragmas: "" 62 | CompactNamespaces: false 63 | ConstructorInitializerIndentWidth: 4 64 | ContinuationIndentWidth: 4 65 | Cpp11BracedListStyle: true 66 | DeriveLineEnding: false 67 | DerivePointerAlignment: false 68 | DisableFormat: false 69 | EmptyLineAfterAccessModifier: Never 70 | EmptyLineBeforeAccessModifier: Always 71 | ExperimentalAutoDetectBinPacking: false 72 | FixNamespaceComments: true 73 | ForEachMacros: [ ] 74 | IfMacros: [ ] 75 | IncludeBlocks: Preserve 76 | IncludeCategories: 77 | - Regex: '".+"' 78 | Priority: 1 79 | - Regex: '<.+>' 80 | Priority: 2 81 | IncludeIsMainRegex: "" 82 | IncludeIsMainSourceRegex: "" 83 | IndentAccessModifiers: false 84 | IndentCaseBlocks: false 85 | IndentCaseLabels: false 86 | IndentExternBlock: Indent 87 | IndentGotoLabels: true 88 | IndentPPDirectives: BeforeHash 89 | IndentRequiresClause: false 90 | IndentWidth: 4 91 | IndentWrappedFunctionNames: false 92 | InsertBraces: true 93 | InsertNewlineAtEOF: true 94 | InsertTrailingCommas: None 95 | JavaImportGroups: [ ] 96 | JavaScriptQuotes: Double 97 | JavaScriptWrapImports: true 98 | KeepEmptyLinesAtTheStartOfBlocks: false 99 | LambdaBodyIndentation: OuterScope 100 | Language: Cpp 101 | MacroBlockBegin: "" 102 | MacroBlockEnd: "" 103 | MaxEmptyLinesToKeep: 1 104 | NamespaceIndentation: None 105 | NamespaceMacros: [ ] 106 | ObjCBinPackProtocolList: Auto 107 | ObjCBlockIndentWidth: 4 108 | ObjCBreakBeforeNestedBlockParam: true 109 | ObjCSpaceAfterProperty: false 110 | ObjCSpaceBeforeProtocolList: false 111 | PPIndentWidth: -1 112 | PackConstructorInitializers: Never 113 | PenaltyBreakAssignment: 1000 114 | PenaltyBreakBeforeFirstCallParameter: 1000 115 | PenaltyBreakComment: 0 116 | PenaltyBreakFirstLessLess: 0 117 | PenaltyBreakOpenParenthesis: 0 118 | PenaltyBreakString: 0 119 | PenaltyBreakTemplateDeclaration: 0 120 | PenaltyExcessCharacter: 10000 121 | PenaltyIndentedWhitespace: 0 122 | PenaltyReturnTypeOnItsOwnLine: 1000 123 | PointerAlignment: Left 124 | QualifierAlignment: Custom 125 | QualifierOrder: [ 'static', 'constexpr', 'inline', 'const', 'volatile', 'restrict', 'type' ] 126 | RawStringFormats: [ ] 127 | ReferenceAlignment: Pointer 128 | ReflowComments: true 129 | RemoveBracesLLVM: false 130 | RequiresClausePosition: OwnLine 131 | SeparateDefinitionBlocks: Always 132 | ShortNamespaceLines: 1 133 | SortIncludes: CaseSensitive 134 | SortJavaStaticImport: After 135 | SortUsingDeclarations: true 136 | SpaceAfterCStyleCast: true 137 | SpaceAfterLogicalNot: false 138 | SpaceAfterTemplateKeyword: false 139 | SpaceAroundPointerQualifiers: Before 140 | SpaceBeforeAssignmentOperators: true 141 | SpaceBeforeCaseColon: false 142 | SpaceBeforeCpp11BracedList: true 143 | SpaceBeforeCtorInitializerColon: true 144 | SpaceBeforeInheritanceColon: true 145 | SpaceBeforeParens: ControlStatements 146 | SpaceBeforeRangeBasedForLoopColon: true 147 | SpaceBeforeSquareBrackets: false 148 | SpaceInEmptyBlock: false 149 | SpaceInEmptyParentheses: false 150 | SpacesBeforeTrailingComments: 1 151 | SpacesInAngles: Never 152 | SpacesInCStyleCastParentheses: false 153 | SpacesInConditionalStatement: false 154 | SpacesInContainerLiterals: false 155 | SpacesInLineCommentPrefix: 156 | Minimum: 1 157 | Maximum: 1 158 | SpacesInParentheses: false 159 | SpacesInSquareBrackets: false 160 | Standard: c++20 161 | StatementAttributeLikeMacros: [ ] 162 | StatementMacros: [ ] 163 | TabWidth: 4 164 | TypenameMacros: [ ] 165 | UseCRLF: false 166 | UseTab: Always 167 | WhitespaceSensitiveMacros: [ ] 168 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | -bugprone-switch-missing-default-case, 3 | -hicpp-exception-baseclass, 4 | -misc-no-recursion, 5 | -modernize-return-braced-init-list, 6 | -modernize-use-auto, 7 | -modernize-use-nodiscard, 8 | -*-use-designated-initializers, 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | guidelines = 128 7 | guidelines_style = 1px dotted white 8 | indent_style = tab 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.*/ 2 | /build/ 3 | /cmake-build-* 4 | /out/ 5 | CMakeFiles/ 6 | 7 | CMakeCache.txt 8 | CMakeUserPresets.json 9 | 10 | *.sln 11 | *.vcxproj 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.29) 2 | project(cero) 3 | 4 | set(CMAKE_POLICY_VERSION_MINIMUM 3.5) 5 | set(CMAKE_CXX_STANDARD 23) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | if (WIN32) 10 | set(system_extension win) 11 | add_compile_definitions(CERO_SYSTEM_WINDOWS) 12 | elseif (UNIX) 13 | set(system_extension posix) 14 | add_compile_definitions(CERO_SYSTEM_POSIX) 15 | endif () 16 | 17 | set(source_filter_regex .+/[^.]+\\.cpp|.+\\.${system_extension}\\.cpp) 18 | 19 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 20 | add_compile_definitions(CERO_COMPILER_MSVC) 21 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 22 | add_compile_definitions(CERO_COMPILER_CLANG) 23 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 24 | add_compile_definitions(CERO_COMPILER_GCC) 25 | endif () 26 | 27 | add_subdirectory(src) 28 | add_subdirectory(tests) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💠 Cero 2 | 3 | An experiment in trying to create a general-purpose programming language suitable for systems programming of any kind, focusing on performance, convenience and safety. 4 | 5 | > **Warning** 6 | > This project is in a pre-alpha stage and there is no functioning compiler or proper documentation. Although many core characteristics of the language are decided, semantic details and syntax of the language are subject to change. 7 | 8 | ## Contributing 9 | If you would like to contribute to Cero, consider contacting me or just straight up open a pull request. -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE sources CONFIGURE_DEPENDS *) 2 | list(FILTER sources INCLUDE REGEX ${source_filter_regex}) 3 | 4 | add_library(cero STATIC ${sources}) 5 | target_precompile_headers(cero PUBLIC Pch.hpp) 6 | target_include_directories(cero PUBLIC .) 7 | 8 | find_package(cxxopts REQUIRED) 9 | find_package(fmt CONFIG REQUIRED) 10 | 11 | find_package(PkgConfig REQUIRED) 12 | pkg_check_modules(gmp REQUIRED IMPORTED_TARGET gmp) 13 | 14 | target_link_libraries(cero PUBLIC 15 | cxxopts::cxxopts 16 | fmt::fmt 17 | PkgConfig::gmp 18 | ) 19 | -------------------------------------------------------------------------------- /src/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "cero/driver/Run.hpp" 2 | 3 | int main(int argc, char* argv[]) { 4 | if (argc < 1) { 5 | return EXIT_FAILURE; 6 | } 7 | 8 | std::span args(argv + 1, argv + argc); 9 | bool succeeded = cero::run(args); 10 | if (succeeded) { 11 | return EXIT_SUCCESS; 12 | } else { 13 | return EXIT_FAILURE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Pch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | -------------------------------------------------------------------------------- /src/cero/driver/BuildCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "BuildCommand.hpp" 2 | 3 | #include "cero/report/ConsoleReporter.hpp" 4 | #include "cero/report/MessageArgs.hpp" 5 | #include "cero/syntax/Lexer.hpp" 6 | #include "cero/syntax/Parser.hpp" 7 | 8 | namespace cero { 9 | 10 | namespace { 11 | 12 | void build_source(const SourceGuard& source, Reporter& reporter, const CompilerOptions& options) { 13 | auto token_stream = run_lexer(source, reporter, false, options.tab_size); 14 | if (options.print_tokens) { 15 | fmt::println("{}", token_stream.to_string(source, options.tab_size)); 16 | } 17 | 18 | auto ast = run_parser(token_stream, source, reporter, options.tab_size); 19 | if (options.print_ast) { 20 | fmt::println("{}", ast.to_string(source, options.tab_size)); 21 | } 22 | } 23 | 24 | } // namespace 25 | 26 | bool run_build_command(const CompilerOptions& options) { 27 | auto source = Source::from_file(options.path); 28 | 29 | ConsoleReporter reporter; 30 | try_build_source(source, reporter, options); 31 | 32 | return !reporter.has_errors(); 33 | } 34 | 35 | void try_build_source(const Source& source, Reporter& reporter, const CompilerOptions& options) { 36 | if (auto lock_result = source.lock()) { 37 | build_source(*lock_result, reporter, options); 38 | } else { 39 | auto& error = lock_result.error(); 40 | const auto error_code = static_cast(error.value()); 41 | 42 | const CodeLocation blank {source.get_name()}; 43 | if (error_code == std::errc::no_such_file_or_directory) { 44 | reporter.report(blank, Message::FileNotFound, MessageArgs()); 45 | } else { 46 | reporter.report(blank, Message::CouldNotOpenFile, MessageArgs(error.message())); 47 | } 48 | } 49 | } 50 | 51 | } // namespace cero 52 | -------------------------------------------------------------------------------- /src/cero/driver/BuildCommand.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/driver/CompilerOptions.hpp" 4 | #include "cero/report/Reporter.hpp" 5 | #include "cero/syntax/Source.hpp" 6 | 7 | namespace cero { 8 | 9 | /// Perform a build with the given options. 10 | bool run_build_command(const CompilerOptions& options); 11 | 12 | /// Build a single source input with the given reporter and options. 13 | void try_build_source(const Source& source, Reporter& reporter, const CompilerOptions& options); 14 | 15 | } // namespace cero 16 | -------------------------------------------------------------------------------- /src/cero/driver/CompilerOptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | struct CompilerOptions { 6 | std::string_view path; 7 | uint8_t tab_size = 4; 8 | bool print_tokens = false; 9 | bool print_ast = false; 10 | 11 | static std::optional from(std::span args) { 12 | return CompilerOptions {}; 13 | } 14 | }; 15 | 16 | } // namespace cero 17 | -------------------------------------------------------------------------------- /src/cero/driver/Run.cpp: -------------------------------------------------------------------------------- 1 | #include "Run.hpp" 2 | 3 | #include "cero/driver/BuildCommand.hpp" 4 | #include "cero/report/ConsoleReporter.hpp" 5 | #include "cero/util/Assert.hpp" 6 | 7 | namespace cero { 8 | 9 | bool run(std::span args) { 10 | ConsoleReporter::init_console_environment(); 11 | 12 | if (auto options = CompilerOptions::from(args)) { 13 | return run_build_command(*options); 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | } // namespace cero 20 | -------------------------------------------------------------------------------- /src/cero/driver/Run.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// Entry point for the compiler with the given command line arguments. 6 | bool run(std::span args); 7 | 8 | } // namespace cero 9 | -------------------------------------------------------------------------------- /src/cero/report/CodeLocation.cpp: -------------------------------------------------------------------------------- 1 | #include "CodeLocation.hpp" 2 | 3 | namespace cero { 4 | 5 | std::string CodeLocation::to_string() const { 6 | return fmt::format("{}:{}:{}", source_path, line, column); 7 | } 8 | 9 | std::string CodeLocation::to_short_string() const { 10 | return fmt::format("[{}:{}]", line, column); 11 | } 12 | 13 | } // namespace cero 14 | -------------------------------------------------------------------------------- /src/cero/report/CodeLocation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// A location in Cero source code. 6 | struct CodeLocation { 7 | std::string_view source_path; 8 | uint32_t line = 0; 9 | uint32_t column = 0; 10 | 11 | bool operator==(const CodeLocation&) const = default; 12 | 13 | /// Creates a string for diagnostic messages. 14 | std::string to_string() const; 15 | 16 | /// Creates a string containing only the line and column, such as for AST dumps. 17 | std::string to_short_string() const; 18 | }; 19 | 20 | constexpr inline uint8_t DefaultTabSize = 4; 21 | 22 | } // namespace cero 23 | -------------------------------------------------------------------------------- /src/cero/report/ConsoleReporter.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleReporter.hpp" 2 | 3 | #include "Message.hpp" 4 | 5 | namespace cero { 6 | 7 | void ConsoleReporter::handle_report(CodeLocation location, MessageLevel message_level, std::string message_text) { 8 | if (message_level == MessageLevel::Error) { 9 | has_errors_ = true; 10 | } 11 | 12 | auto location_str = location.to_string(); 13 | auto msg_level_str = get_message_level_string(message_level); 14 | fmt::println("{}: {}: {}", location_str, msg_level_str, message_text); 15 | } 16 | 17 | bool ConsoleReporter::has_errors() const { 18 | return has_errors_; 19 | } 20 | 21 | } // namespace cero 22 | -------------------------------------------------------------------------------- /src/cero/report/ConsoleReporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Reporter.hpp" 4 | 5 | namespace cero { 6 | 7 | /// Prints reports to the console. 8 | class ConsoleReporter : public Reporter { 9 | public: 10 | /// Sets up color output and UTF-8 output. 11 | static void init_console_environment(); 12 | 13 | bool has_errors() const override; 14 | 15 | private: 16 | bool has_errors_ = false; 17 | 18 | void handle_report(CodeLocation location, MessageLevel message_level, std::string message_text) override; 19 | }; 20 | 21 | } // namespace cero 22 | -------------------------------------------------------------------------------- /src/cero/report/ConsoleReporter.posix.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleReporter.hpp" 2 | 3 | namespace cero { 4 | 5 | void ConsoleReporter::init_console_environment() { 6 | } 7 | 8 | } // namespace cero 9 | -------------------------------------------------------------------------------- /src/cero/report/ConsoleReporter.win.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleReporter.hpp" 2 | 3 | #include "cero/util/Assert.hpp" 4 | #include "cero/util/SystemError.hpp" 5 | #include "cero/util/WindowsApi.hpp" 6 | 7 | namespace cero { 8 | 9 | void ConsoleReporter::init_console_environment() { 10 | BOOL success = ::SetConsoleOutputCP(CP_UTF8); 11 | cero_assert(success, "could not set console output code page, {}", get_last_system_error_message()); 12 | } 13 | 14 | } // namespace cero 15 | -------------------------------------------------------------------------------- /src/cero/report/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "Message.hpp" 2 | 3 | #include "cero/util/Assert.hpp" 4 | 5 | namespace cero { 6 | 7 | std::string_view get_message_format_string(Message message) { 8 | using enum Message; 9 | switch (message) { 10 | case FileNotFound: return "file not found"; 11 | case CouldNotOpenFile: return "could not open file, system error: \"{}\""; 12 | case SourceFileTooLarge: return "source file is too large, maximum allowed is {} bytes"; 13 | case InvalidCharacter: return "invalid character `0x{:x}`"; 14 | case MissingClosingQuote: return "missing closing quote"; 15 | case UnterminatedBlockComment: return "block comment must be closed with `*/`"; 16 | case ExpectFuncStructEnum: return "expected function, struct or enum, but found {}"; 17 | case ExpectParenAfterFuncName: return "expected `(` after function name, but found {}"; 18 | case ExpectType: return "expected a type, but found {}"; 19 | case ExpectParamName: return "expected name for parameter, but found {}"; 20 | case ExpectParenAfterParams: return "expected `)` after parameters, but found {}"; 21 | case ExpectParenAfterOutputs: return "expected `)` after function outputs, but found {}"; 22 | case ExpectBraceBeforeFuncBody: return "expected `{{` before function body, but found {}"; 23 | case ExpectNameAfterLet: return "expected a name after `let` specifier, but found {}"; 24 | case ExpectNameAfterDeclarationType: return "expected a name after type in declaration, but found {}"; 25 | case ExpectExpr: return "expected expression, but found {}"; 26 | case ExpectSemicolon: return "expected a `;`, but found {}"; 27 | case UnnecessarySemicolon: return "unnecessary semicolon"; 28 | case NameCannotAppearHere: return "name cannot appear here"; 29 | case ExpectNameAfterDot: return "expected a member name after `.`, but found {}"; 30 | case ExpectColonOrBlockInIfExpr: return "expected `:`or block after `if` condition, but found {}"; 31 | case ExpectBlockAfterIfCond: return "expected block after `if` condition, but found {}"; 32 | case ExpectBlockAfterElse: return "expected block after `else`, but found {}"; 33 | case UnnecessaryColonBeforeBlock: return "`:` is unnecessary before a block"; 34 | case ExpectElse: return "expected `else` after `if` expression, but found {}"; 35 | case ExpectBlockAfterWhileCond: return "expected block after `while` condition, but found {}"; 36 | case ExpectClosingParen: return "expected closing `)`, but found {}"; 37 | case ExpectBracketAfterIndex: return "expected `]` after index expression, but found {}"; 38 | case ExpectBracketAfterArrayBound: return "expected `]` after array bound, but found {}"; 39 | case ExpectBraceAfterPermission: return "expected `}}` after permission arguments, but found {}"; 40 | case ExpectArrowAfterFuncTypeParams: return "expected `->` after parameters for function type, but found {}"; 41 | case FuncTypeDefaultArgument: return "parameter in function type cannot have default argument"; 42 | case AmbiguousOperatorMixing: return "mixing operator `{}` with operator `{}` is ambiguous"; 43 | case ExpectNameForStruct: return "expected name for struct, but found {}"; 44 | case ExpectNameForEnum: return "expected name for enum, but found {}"; 45 | } 46 | assert_unreachable(); 47 | } 48 | 49 | std::string_view get_message_level_string(MessageLevel message_level) { 50 | switch (message_level) { 51 | case MessageLevel::Error: return "error"; 52 | case MessageLevel::Warning: return "warning"; 53 | case MessageLevel::Help: return "help"; 54 | case MessageLevel::Note: return "note"; 55 | } 56 | assert_unreachable(); 57 | } 58 | 59 | MessageLevel get_default_message_level(Message message) { 60 | using enum Message; 61 | switch (message) { 62 | case UnterminatedBlockComment: 63 | case UnnecessaryColonBeforeBlock: 64 | case UnnecessarySemicolon: return MessageLevel::Warning; 65 | default: return MessageLevel::Error; 66 | } 67 | } 68 | 69 | } // namespace cero 70 | -------------------------------------------------------------------------------- /src/cero/report/Message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// A distinct diagnostic emitted by the compiler. 6 | enum class Message : uint8_t { 7 | FileNotFound, 8 | CouldNotOpenFile, 9 | SourceFileTooLarge, 10 | InvalidCharacter, 11 | MissingClosingQuote, 12 | UnterminatedBlockComment, 13 | ExpectFuncStructEnum, 14 | ExpectParenAfterFuncName, 15 | ExpectType, 16 | ExpectParamName, 17 | ExpectParenAfterParams, 18 | ExpectParenAfterOutputs, 19 | ExpectBraceBeforeFuncBody, 20 | ExpectNameAfterLet, 21 | ExpectNameAfterDeclarationType, 22 | ExpectExpr, 23 | ExpectSemicolon, 24 | UnnecessarySemicolon, 25 | NameCannotAppearHere, 26 | ExpectNameAfterDot, 27 | ExpectColonOrBlockInIfExpr, 28 | ExpectBlockAfterIfCond, 29 | ExpectBlockAfterElse, 30 | UnnecessaryColonBeforeBlock, 31 | ExpectElse, 32 | ExpectBlockAfterWhileCond, 33 | ExpectClosingParen, 34 | ExpectBracketAfterIndex, 35 | ExpectBracketAfterArrayBound, 36 | ExpectBraceAfterPermission, 37 | ExpectArrowAfterFuncTypeParams, 38 | FuncTypeDefaultArgument, 39 | AmbiguousOperatorMixing, 40 | ExpectNameForStruct, 41 | ExpectNameForEnum, 42 | }; 43 | 44 | /// Get the format string for outputting a specific diagnostic. 45 | std::string_view get_message_format_string(Message message); 46 | 47 | /// Indicates how severe a diagnostic is. 48 | enum class MessageLevel : uint8_t { 49 | Error, 50 | Warning, 51 | Help, 52 | Note, 53 | }; 54 | 55 | /// Creates a user-facing string representation of the message level. 56 | std::string_view get_message_level_string(MessageLevel message_level); 57 | 58 | /// Gets the message level a message should be emitted as by default. 59 | MessageLevel get_default_message_level(Message message); 60 | 61 | } // namespace cero 62 | -------------------------------------------------------------------------------- /src/cero/report/MessageArgs.cpp: -------------------------------------------------------------------------------- 1 | #include "MessageArgs.hpp" 2 | 3 | namespace cero { 4 | 5 | // TODO: Remove this hack when vcpkg has fmt at 11.1.0 6 | using data_member_ptr_t = std::vector> 7 | fmt::dynamic_format_arg_store::*; 8 | 9 | data_member_ptr_t get_data_member_ptr(); 10 | 11 | template 12 | struct public_cast { 13 | friend data_member_ptr_t get_data_member_ptr() { 14 | return Ptr; 15 | } 16 | }; 17 | 18 | template struct public_cast<&fmt::dynamic_format_arg_store::data_>; 19 | 20 | bool MessageArgs::valid_for_message(Message message) const { 21 | std::string_view msg_format = get_message_format_string(message); 22 | 23 | size_t placeholder_count = 0; 24 | bool open = false; 25 | for (char c : msg_format) { 26 | if (c == '{') { 27 | open ^= true; // flip value, so escaped brace is not counted as an open brace 28 | } else if (c == '}' && open) { 29 | open = false; 30 | ++placeholder_count; 31 | } 32 | } 33 | 34 | return (store.*get_data_member_ptr()).size() == placeholder_count; 35 | } 36 | 37 | } // namespace cero 38 | -------------------------------------------------------------------------------- /src/cero/report/MessageArgs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Message.hpp" 4 | 5 | namespace cero { 6 | 7 | /// Stores formatting arguments for diagnostic messages. 8 | struct MessageArgs { 9 | fmt::dynamic_format_arg_store store; 10 | 11 | explicit MessageArgs(auto&&... args) { 12 | (store.push_back(args), ...); 13 | } 14 | 15 | /// Checks if the number of arguments matches the number expected for the given message, since fmt::vformat will not error 16 | /// if too many arguments are supplied. 17 | bool valid_for_message(Message message) const; 18 | }; 19 | 20 | } // namespace cero 21 | -------------------------------------------------------------------------------- /src/cero/report/Reporter.cpp: -------------------------------------------------------------------------------- 1 | #include "Reporter.hpp" 2 | 3 | #include "cero/util/Assert.hpp" 4 | 5 | namespace cero { 6 | 7 | void Reporter::report(CodeLocation location, Message message, MessageArgs args) { 8 | cero_assert(args.valid_for_message(message), "incorrect number of message arguments"); 9 | 10 | MessageLevel message_level = get_default_message_level(message); 11 | std::string_view format = get_message_format_string(message); 12 | handle_report(location, message_level, fmt::vformat(format, args.store)); 13 | } 14 | 15 | } // namespace cero 16 | -------------------------------------------------------------------------------- /src/cero/report/Reporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CodeLocation.hpp" 4 | #include "Message.hpp" 5 | #include "MessageArgs.hpp" 6 | 7 | namespace cero { 8 | 9 | /// Interface for implementing different ways to report diagnostics. 10 | class Reporter { 11 | public: 12 | virtual ~Reporter() = default; 13 | 14 | /// Reports a diagnostic message. 15 | void report(CodeLocation location, Message message, MessageArgs args); 16 | 17 | virtual bool has_errors() const = 0; 18 | 19 | private: 20 | /// Will be called by the report method. Override to handle how the report is actually emitted. 21 | virtual void handle_report(CodeLocation location, MessageLevel message_level, std::string message_text) = 0; 22 | }; 23 | 24 | } // namespace cero 25 | -------------------------------------------------------------------------------- /src/cero/syntax/Ast.cpp: -------------------------------------------------------------------------------- 1 | #include "Ast.hpp" 2 | 3 | #include "cero/syntax/AstToString.hpp" 4 | 5 | namespace cero { 6 | 7 | uint32_t Ast::num_nodes() const { 8 | return static_cast(nodes_.size()); 9 | } 10 | 11 | bool Ast::has_errors() const { 12 | return has_errors_; 13 | } 14 | 15 | std::span Ast::array() const { 16 | return {nodes_}; 17 | } 18 | 19 | std::string Ast::to_string(const SourceGuard& source, uint8_t tab_size) const { 20 | return ast_to_string(*this, source, tab_size); 21 | } 22 | 23 | Ast::Ast(const TokenStream& token_stream) { 24 | nodes_.reserve(token_stream.num_tokens()); 25 | } 26 | 27 | Ast::NodeIndex Ast::store(AstNode&& node) { 28 | auto index = static_cast(nodes_.size()); 29 | nodes_.emplace_back(std::move(node)); 30 | return index; 31 | } 32 | 33 | Ast::NodeIndex Ast::store_parent_of(NodeIndex first_child, AstNode&& node) { 34 | nodes_.insert(nodes_.begin() + static_cast(first_child), std::move(node)); 35 | // after the insert, the index of the first_child is now the index of the newly inserted parent node 36 | return first_child; 37 | } 38 | 39 | AstNode& Ast::get(Ast::NodeIndex index) { 40 | return nodes_[index]; 41 | } 42 | 43 | void Ast::undo_nodes_from_lookahead(NodeIndex first) { 44 | nodes_.erase(nodes_.begin() + static_cast(first), nodes_.end()); 45 | } 46 | 47 | } // namespace cero 48 | -------------------------------------------------------------------------------- /src/cero/syntax/Ast.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/AstNode.hpp" 4 | #include "cero/syntax/Source.hpp" 5 | #include "cero/syntax/TokenStream.hpp" 6 | 7 | namespace cero { 8 | 9 | /// Stores the abstract syntax tree for one source file. Contains no type information and is immutable. The underlying dynamic 10 | /// array stores the AST nodes in pre-order. 11 | class Ast { 12 | public: 13 | /// Number of AST nodes. 14 | uint32_t num_nodes() const; 15 | 16 | /// Whether syntax errors were encountered during parsing. 17 | bool has_errors() const; 18 | 19 | /// Get a view of the underlying storage. 20 | std::span array() const; 21 | 22 | /// Creates a tree-like string representation of the AST. 23 | std::string to_string(const SourceGuard& source, uint8_t tab_size) const; 24 | 25 | private: 26 | std::vector nodes_; 27 | bool has_errors_ = false; 28 | uint16_t current_num_children_ = 0; 29 | uint32_t current_num_descendants_ = 0; 30 | 31 | using NodeIndex = uint32_t; 32 | 33 | /// Reserves storage for the AST based on the number of tokens. 34 | explicit Ast(const TokenStream& token_stream); 35 | 36 | /// Stores a new node in the AST, positioning it as the rightmost child of the currently rightmost node. TODO: Not true 37 | NodeIndex store(AstNode&& node); 38 | 39 | /// Stores a new node in the AST as the parent of a node already in the AST. For performance reasons, should only be used 40 | /// when it cannot be known that a node must be the parent of an already stored node until after the child node was stored. 41 | NodeIndex store_parent_of(NodeIndex first_child, AstNode&& node); 42 | 43 | AstNode& get(NodeIndex index); 44 | 45 | NodeIndex next_index() const { 46 | return static_cast(nodes_.size()); 47 | } 48 | 49 | void undo_nodes_from_lookahead(NodeIndex first); 50 | 51 | friend class Parser; 52 | }; 53 | 54 | } // namespace cero 55 | -------------------------------------------------------------------------------- /src/cero/syntax/AstCursor.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCursor.hpp" 2 | 3 | #include "cero/util/Assert.hpp" 4 | #include "cero/util/ScopedAssign.hpp" 5 | 6 | namespace cero { 7 | 8 | AstCursor::AstCursor(const Ast& ast) : 9 | it_(ast.array().begin()), 10 | end_(ast.array().end()), 11 | num_children_to_visit_(it_->num_children()) { 12 | } 13 | 14 | void AstCursor::visit_one(AstVisitor& visitor) { 15 | cero_assert(it_ != end_, "Cursor is at end."); 16 | it_++->visit(visitor); 17 | } 18 | 19 | void AstCursor::visit_all(AstVisitor& visitor) { 20 | ScopedAssign _(num_children_to_visit_, it_->num_children()); 21 | it_++->visit(visitor); 22 | 23 | while (num_children_to_visit_ > 0) { 24 | visit_all(visitor); 25 | --num_children_to_visit_; 26 | } 27 | } 28 | 29 | void AstCursor::visit_child(AstVisitor& visitor) { 30 | cero_assert_debug(num_children_to_visit_ > 0, "Attempted to visit child but current node has no children left to visit."); 31 | 32 | if (num_children_to_visit_ > 0) { 33 | visit_all(visitor); 34 | --num_children_to_visit_; 35 | } 36 | } 37 | 38 | void AstCursor::visit_children(uint32_t n, AstVisitor& visitor) { 39 | cero_assert_debug(n <= num_children_to_visit_, "Attempted to visit more children than the current node has left to visit."); 40 | n = std::min(n, num_children_to_visit_); 41 | 42 | while (n > 0) { 43 | visit_all(visitor); 44 | --num_children_to_visit_; 45 | --n; 46 | } 47 | } 48 | 49 | uint32_t AstCursor::num_children_to_visit() const { 50 | return num_children_to_visit_; 51 | } 52 | 53 | } // namespace cero 54 | -------------------------------------------------------------------------------- /src/cero/syntax/AstCursor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Ast.hpp" 4 | 5 | namespace cero { 6 | 7 | /// Utility for traversing an AST. Its methods are reentrant, meaning you can call a visit method recursively while a visit 8 | /// method on the same cursor has not yet completed. 9 | class AstCursor { 10 | public: 11 | /// Creates a cursor positioned at the root of the given AST. 12 | explicit AstCursor(const Ast& ast); 13 | 14 | void visit_one(AstVisitor& visitor); 15 | 16 | /// Traverse the entire AST using the given visitor. 17 | void visit_all(AstVisitor& visitor); 18 | 19 | /// Visit one child of the current node and all children of that child node. 20 | void visit_child(AstVisitor& visitor); 21 | 22 | /// Visit the given number of children of the current node and all children of those child nodes. 23 | void visit_children(uint32_t n, AstVisitor& visitor); 24 | 25 | /// Gets the number of children of the current node that have not yet been visited. 26 | uint32_t num_children_to_visit() const; 27 | 28 | private: 29 | std::span::iterator it_; 30 | std::span::iterator end_; 31 | uint32_t num_children_to_visit_; 32 | }; 33 | 34 | } // namespace cero 35 | -------------------------------------------------------------------------------- /src/cero/syntax/AstNode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/AstNodeKind.hpp" 4 | #include "cero/syntax/AstVisitor.hpp" 5 | #include "cero/util/Assert.hpp" 6 | #include "cero/util/Traits.hpp" 7 | 8 | namespace cero { 9 | 10 | union AstNode { 11 | public: 12 | AstNodeKind get_kind() const { 13 | return Root_.header.kind; 14 | } 15 | 16 | uint32_t get_offset() const { 17 | return Root_.header.offset; 18 | } 19 | 20 | uint32_t num_children() const { 21 | switch (Root_.header.kind) { 22 | #define CERO_AST_NODE_KIND(X) \ 23 | case AstNodeKind::X: return X##_.num_children(); 24 | CERO_AST_NODE_KINDS 25 | #undef CERO_AST_NODE_KIND 26 | } 27 | assert_unreachable(); 28 | } 29 | 30 | void visit(AstVisitor& visitor) const { 31 | switch (Root_.header.kind) { 32 | #define CERO_AST_NODE_KIND(X) \ 33 | case AstNodeKind::X: visitor.visit(X##_); break; 34 | CERO_AST_NODE_KINDS 35 | #undef CERO_AST_NODE_KIND 36 | } 37 | } 38 | 39 | template 40 | T& as() { 41 | #define CERO_AST_NODE_KIND(X) \ 42 | if constexpr (std::is_same_v) { \ 43 | if (Root_.header.kind == AstNodeKind::X) return X##_; \ 44 | \ 45 | assert_fail("node does not hold expected type"); \ 46 | } else 47 | 48 | CERO_AST_NODE_KINDS 49 | #undef CERO_AST_NODE_KIND 50 | { 51 | static_assert(always_false, "T must be an AST node type."); 52 | } 53 | } 54 | 55 | template 56 | const T& as() const { 57 | #define CERO_AST_NODE_KIND(X) \ 58 | if constexpr (std::is_same_v) { \ 59 | if (Root_.header.kind == AstNodeKind::X) return X##_; \ 60 | \ 61 | assert_fail("node does not hold expected type"); \ 62 | } else 63 | CERO_AST_NODE_KINDS 64 | #undef CERO_AST_NODE_KIND 65 | { 66 | static_assert(always_false, "T must be an AST node type."); 67 | } 68 | } 69 | 70 | template 71 | T* get() { 72 | #define CERO_AST_NODE_KIND(X) \ 73 | if constexpr (std::is_same_v) { \ 74 | return Root_.header.kind == AstNodeKind::X ? &X##_ : nullptr; \ 75 | } else 76 | CERO_AST_NODE_KINDS 77 | #undef CERO_AST_NODE_KIND 78 | { 79 | static_assert(always_false, "T must be an AST node type."); 80 | } 81 | } 82 | 83 | template 84 | const T* get() const { 85 | #define CERO_AST_NODE_KIND(X) \ 86 | if constexpr (std::is_same_v) { \ 87 | return Root_.header.kind == AstNodeKind::X ? &X##_ : nullptr; \ 88 | } else 89 | CERO_AST_NODE_KINDS 90 | #undef CERO_AST_NODE_KIND 91 | { 92 | static_assert(always_false, "T must be an AST node type."); 93 | } 94 | } 95 | 96 | ~AstNode() { 97 | switch (Root_.header.kind) { 98 | #define CERO_AST_NODE_KIND(X) \ 99 | case AstNodeKind::X: X##_.~Ast##X(); break; 100 | CERO_AST_NODE_KINDS 101 | #undef CERO_AST_NODE_KIND 102 | } 103 | } 104 | 105 | AstNode(AstNode&& other) noexcept { 106 | switch (other.Root_.header.kind) { 107 | #define CERO_AST_NODE_KIND(X) \ 108 | case AstNodeKind::X: new (&X##_) Ast##X(std::move(other.X##_)); break; 109 | CERO_AST_NODE_KINDS 110 | #undef CERO_AST_NODE_KIND 111 | } 112 | } 113 | 114 | AstNode& operator=(AstNode&& other) noexcept { 115 | this->~AstNode(); 116 | new (this) AstNode(std::move(other)); 117 | return *this; 118 | } 119 | 120 | private: 121 | #define CERO_AST_NODE_KIND(X) Ast##X X##_; 122 | CERO_AST_NODE_KINDS 123 | #undef CERO_AST_NODE_KIND 124 | 125 | #define CERO_AST_NODE_KIND(X) \ 126 | AstNode(Ast##X&& node) : \ 127 | X##_(std::move(node)) { \ 128 | } 129 | CERO_AST_NODE_KINDS 130 | #undef CERO_AST_NODE_KIND 131 | 132 | friend class Parser; 133 | friend class Ast; 134 | }; 135 | 136 | } // namespace cero 137 | -------------------------------------------------------------------------------- /src/cero/syntax/AstNodeKind.cpp: -------------------------------------------------------------------------------- 1 | #include "AstNodeKind.hpp" 2 | 3 | #include "cero/util/Assert.hpp" 4 | 5 | namespace cero { 6 | 7 | std::string_view unary_operator_to_string(UnaryOperator op) { 8 | switch (op) { 9 | using enum UnaryOperator; 10 | case PreInc: return "prefix ++"; 11 | case PreDec: return "prefix --"; 12 | case PostInc: return "postfix ++"; 13 | case PostDec: return "postfix --"; 14 | case Addr: return "&"; 15 | case Deref: return "^"; 16 | case Neg: return "-"; 17 | case Not: return "~"; 18 | } 19 | assert_unreachable(); 20 | } 21 | 22 | std::string_view binary_operator_to_string(BinaryOperator op) { 23 | switch (op) { 24 | using enum BinaryOperator; 25 | case Add: return "+"; 26 | case Sub: return "-"; 27 | case Mul: return "*"; 28 | case Div: return "/"; 29 | case Rem: return "%"; 30 | case Pow: return "**"; 31 | case LogicAnd: return "&&"; 32 | case LogicOr: return "||"; 33 | case BitAnd: return "&"; 34 | case BitOr: return "|"; 35 | case Xor: return "~"; 36 | case Shl: return "<<"; 37 | case Shr: return ">>"; 38 | case Eq: return "=="; 39 | case NotEq: return "!="; 40 | case Less: return "<"; 41 | case LessEq: return "<="; 42 | case Greater: return ">"; 43 | case GreaterEq: return ">="; 44 | case Assign: return "="; 45 | case AddAssign: return "+="; 46 | case SubAssign: return "-="; 47 | case MulAssign: return "*="; 48 | case DivAssign: return "/="; 49 | case RemAssign: return "%="; 50 | case PowAssign: return "**="; 51 | case BitAndAssign: return "&="; 52 | case BitOrAssign: return "|="; 53 | case XorAssign: return "~="; 54 | case ShlAssign: return "<<="; 55 | case ShrAssign: return ">>="; 56 | case LogicAndAssign: return "&&="; 57 | case LogicOrAssign: return "||="; 58 | } 59 | assert_unreachable(); 60 | } 61 | 62 | } // namespace cero 63 | -------------------------------------------------------------------------------- /src/cero/syntax/AstNodeKind.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Source.hpp" 4 | 5 | namespace cero { 6 | 7 | using StringId = std::string_view; 8 | 9 | #define CERO_AST_NODE_KINDS \ 10 | CERO_AST_NODE_KIND(Root) \ 11 | CERO_AST_NODE_KIND(StructDefinition) \ 12 | CERO_AST_NODE_KIND(EnumDefinition) \ 13 | CERO_AST_NODE_KIND(FunctionDefinition) \ 14 | CERO_AST_NODE_KIND(FunctionParameter) \ 15 | CERO_AST_NODE_KIND(FunctionOutput) \ 16 | CERO_AST_NODE_KIND(BlockStatement) \ 17 | CERO_AST_NODE_KIND(BindingStatement) \ 18 | CERO_AST_NODE_KIND(IfExpr) \ 19 | CERO_AST_NODE_KIND(WhileLoop) \ 20 | CERO_AST_NODE_KIND(ForLoop) \ 21 | CERO_AST_NODE_KIND(NameExpr) \ 22 | CERO_AST_NODE_KIND(GenericNameExpr) \ 23 | CERO_AST_NODE_KIND(MemberExpr) \ 24 | CERO_AST_NODE_KIND(GroupExpr) \ 25 | CERO_AST_NODE_KIND(CallExpr) \ 26 | CERO_AST_NODE_KIND(IndexExpr) \ 27 | CERO_AST_NODE_KIND(ArrayLiteralExpr) \ 28 | CERO_AST_NODE_KIND(UnaryExpr) \ 29 | CERO_AST_NODE_KIND(BinaryExpr) \ 30 | CERO_AST_NODE_KIND(ReturnExpr) \ 31 | CERO_AST_NODE_KIND(ThrowExpr) \ 32 | CERO_AST_NODE_KIND(BreakExpr) \ 33 | CERO_AST_NODE_KIND(ContinueExpr) \ 34 | CERO_AST_NODE_KIND(NumericLiteralExpr) \ 35 | CERO_AST_NODE_KIND(StringLiteralExpr) \ 36 | CERO_AST_NODE_KIND(PermissionExpr) \ 37 | CERO_AST_NODE_KIND(PointerTypeExpr) \ 38 | CERO_AST_NODE_KIND(ArrayTypeExpr) \ 39 | CERO_AST_NODE_KIND(FunctionTypeExpr) 40 | 41 | enum class AstNodeKind { 42 | #define CERO_AST_NODE_KIND(X) X, 43 | CERO_AST_NODE_KINDS 44 | #undef CERO_AST_NODE_KIND 45 | }; 46 | 47 | template 48 | struct AstNodeHeader { 49 | const AstNodeKind kind : 8 = K; 50 | const SourceOffset offset : SourceOffsetBits = 0; 51 | 52 | AstNodeHeader() = default; 53 | 54 | AstNodeHeader(SourceOffset source_offset) : 55 | offset(source_offset & 0x00ffffffu) { 56 | } 57 | 58 | CodeLocation locate_in(const SourceGuard& source, uint8_t tab_size) const { 59 | return source.locate(offset, tab_size); 60 | } 61 | }; 62 | 63 | struct AstRoot { 64 | AstNodeHeader header; 65 | uint16_t num_definitions = 0; 66 | 67 | uint32_t num_children() const { 68 | return num_definitions; 69 | } 70 | }; 71 | 72 | enum class AccessSpecifier : uint8_t { 73 | None, 74 | Private, 75 | Public 76 | }; 77 | 78 | struct AstStructDefinition { 79 | AstNodeHeader header; 80 | AccessSpecifier access = {}; 81 | StringId name; 82 | 83 | uint32_t num_children() const { 84 | (void) this; 85 | return 0; 86 | } 87 | }; 88 | 89 | struct AstEnumDefinition { 90 | AstNodeHeader header; 91 | AccessSpecifier access = {}; 92 | StringId name; 93 | 94 | uint32_t num_children() const { 95 | (void) this; 96 | return 0; 97 | } 98 | }; 99 | 100 | struct AstFunctionDefinition { 101 | AstNodeHeader header; 102 | AccessSpecifier access = {}; 103 | StringId name; 104 | uint16_t num_parameters = 0; 105 | uint16_t num_outputs = 0; 106 | uint32_t num_statements = 0; 107 | 108 | uint32_t num_children() const { 109 | return num_parameters + num_outputs + num_statements; 110 | } 111 | }; 112 | 113 | enum class ParameterSpecifier : uint8_t { 114 | None, 115 | In, 116 | Var 117 | }; 118 | 119 | struct AstFunctionParameter { 120 | AstNodeHeader header; 121 | ParameterSpecifier specifier = {}; 122 | StringId name; 123 | bool has_default_argument = false; 124 | 125 | uint32_t num_children() const { 126 | return has_default_argument ? 2 : 1; 127 | } 128 | }; 129 | 130 | struct AstFunctionOutput { 131 | AstNodeHeader header; 132 | StringId name; 133 | 134 | static uint32_t num_children() { 135 | return 1; 136 | } 137 | }; 138 | 139 | struct AstBlockStatement { 140 | AstNodeHeader header; 141 | uint32_t num_statements = 0; 142 | 143 | uint32_t num_children() const { 144 | return num_statements; 145 | } 146 | }; 147 | 148 | enum class BindingSpecifier : uint8_t { 149 | Let, 150 | Var, 151 | Const, 152 | Static, 153 | StaticVar 154 | }; 155 | 156 | struct AstBindingStatement { 157 | AstNodeHeader header; 158 | BindingSpecifier specifier = {}; 159 | bool has_type = false; 160 | StringId name; 161 | bool has_initializer = false; 162 | 163 | uint32_t num_children() const { 164 | return (has_type ? 1u : 0u) + (has_initializer ? 1 : 0); 165 | } 166 | }; 167 | 168 | struct AstIfExpr { 169 | AstNodeHeader header; 170 | uint32_t num_then_statements = 0; 171 | uint32_t num_else_statements = 0; 172 | 173 | uint32_t num_children() const { 174 | return 1 + num_then_statements + num_else_statements; 175 | } 176 | }; 177 | 178 | struct AstWhileLoop { 179 | AstNodeHeader header; 180 | uint32_t num_statements = 0; 181 | 182 | uint32_t num_children() const { 183 | return 1 + num_statements; 184 | } 185 | }; 186 | 187 | struct AstForLoop { 188 | AstNodeHeader header; 189 | uint32_t num_statements = 0; 190 | 191 | uint32_t num_children() const { 192 | return 2 + num_statements; 193 | } 194 | }; 195 | 196 | struct AstNameExpr { 197 | AstNodeHeader header; 198 | StringId name; 199 | 200 | static uint32_t num_children() { 201 | return 0; 202 | } 203 | }; 204 | 205 | struct AstGenericNameExpr { 206 | AstNodeHeader header; 207 | StringId name; 208 | uint16_t num_generic_args = 0; 209 | 210 | uint32_t num_children() const { 211 | return num_generic_args; 212 | } 213 | }; 214 | 215 | struct AstMemberExpr { 216 | AstNodeHeader header; 217 | StringId member; 218 | uint16_t num_generic_args = 0; 219 | 220 | uint32_t num_children() const { 221 | return 1 + num_generic_args; 222 | } 223 | }; 224 | 225 | struct AstGroupExpr { 226 | AstNodeHeader header; 227 | uint16_t num_args = 0; 228 | 229 | uint32_t num_children() const { 230 | return num_args; 231 | } 232 | }; 233 | 234 | struct AstCallExpr { 235 | AstNodeHeader header; 236 | uint16_t num_args = 0; 237 | 238 | uint32_t num_children() const { 239 | return 1 + num_args; 240 | } 241 | }; 242 | 243 | struct AstIndexExpr { 244 | AstNodeHeader header; 245 | uint16_t num_args = 0; 246 | 247 | uint32_t num_children() const { 248 | return 1 + num_args; 249 | } 250 | }; 251 | 252 | struct AstArrayLiteralExpr { 253 | AstNodeHeader header; 254 | uint16_t num_elements = 0; 255 | 256 | uint32_t num_children() const { 257 | return num_elements; 258 | } 259 | }; 260 | 261 | enum class UnaryOperator : uint8_t { 262 | PreInc, 263 | PreDec, 264 | PostInc, 265 | PostDec, 266 | Addr, 267 | Deref, 268 | Neg, 269 | Not, 270 | }; 271 | 272 | std::string_view unary_operator_to_string(UnaryOperator op); 273 | 274 | struct AstUnaryExpr { 275 | AstNodeHeader header; 276 | UnaryOperator op = {}; 277 | 278 | static uint32_t num_children() { 279 | return 1; 280 | } 281 | }; 282 | 283 | enum class BinaryOperator : uint8_t { 284 | Add, 285 | Sub, 286 | Mul, 287 | Div, 288 | Rem, 289 | Pow, 290 | BitAnd, 291 | BitOr, 292 | Xor, 293 | Shl, 294 | Shr, 295 | LogicAnd, 296 | LogicOr, 297 | Eq, 298 | NotEq, 299 | Less, 300 | LessEq, 301 | Greater, 302 | GreaterEq, 303 | Assign, 304 | AddAssign, 305 | SubAssign, 306 | MulAssign, 307 | DivAssign, 308 | RemAssign, 309 | PowAssign, 310 | BitAndAssign, 311 | BitOrAssign, 312 | XorAssign, 313 | ShlAssign, 314 | ShrAssign, 315 | LogicAndAssign, 316 | LogicOrAssign 317 | }; 318 | 319 | std::string_view binary_operator_to_string(BinaryOperator op); 320 | 321 | struct AstBinaryExpr { 322 | AstNodeHeader header; 323 | BinaryOperator op = {}; 324 | 325 | static uint32_t num_children() { 326 | return 2; 327 | } 328 | }; 329 | 330 | struct AstReturnExpr { 331 | AstNodeHeader header; 332 | uint16_t num_expressions = 0; 333 | 334 | uint32_t num_children() const { 335 | return num_expressions; 336 | } 337 | }; 338 | 339 | struct AstThrowExpr { 340 | AstNodeHeader header; 341 | bool has_expression = false; 342 | 343 | uint32_t num_children() const { 344 | return has_expression ? 1 : 0; 345 | } 346 | }; 347 | 348 | struct AstBreakExpr { 349 | AstNodeHeader header; 350 | bool has_label = false; 351 | 352 | uint32_t num_children() const { 353 | return has_label ? 1 : 0; 354 | } 355 | }; 356 | 357 | struct AstContinueExpr { 358 | AstNodeHeader header; 359 | bool has_label = false; 360 | 361 | uint32_t num_children() const { 362 | return has_label ? 1 : 0; 363 | } 364 | }; 365 | 366 | enum class NumericLiteralKind : uint8_t { 367 | Decimal, 368 | Hexadecimal, 369 | Binary, 370 | Octal, 371 | Float, 372 | Character 373 | }; 374 | 375 | struct AstNumericLiteralExpr { 376 | AstNodeHeader header; 377 | NumericLiteralKind kind = {}; 378 | 379 | static uint32_t num_children() { 380 | return 0; 381 | } 382 | }; 383 | 384 | struct AstStringLiteralExpr { 385 | AstNodeHeader header; 386 | std::string value; 387 | 388 | static uint32_t num_children() { 389 | return 0; 390 | } 391 | }; 392 | 393 | enum class PermissionSpecifier : uint8_t { 394 | In, 395 | Var, 396 | VarBounded, 397 | VarUnbounded 398 | }; 399 | 400 | struct AstPermissionExpr { 401 | AstNodeHeader header; 402 | PermissionSpecifier specifier = {}; 403 | uint16_t num_args = 0; 404 | 405 | uint32_t num_children() const { 406 | return num_args; 407 | } 408 | }; 409 | 410 | /// AST node for pointer type expressions, i.e. `^var List` or `^bool`. The first child node is optional and is the 411 | /// expression provided for the permission. The second child node is the type expression provided for the pointed-to type. 412 | struct AstPointerTypeExpr { 413 | AstNodeHeader header; 414 | bool has_permission = false; 415 | 416 | static uint32_t num_children() { 417 | return 2; 418 | } 419 | }; 420 | 421 | /// AST node for array type expressions, i.e. `[4]int32` or `[]int32`. The first child node is optional and is the expression 422 | /// provided for the array bound. The second child node is the type expression provided for the element type. 423 | struct AstArrayTypeExpr { 424 | AstNodeHeader header; 425 | bool has_bound = false; 426 | 427 | uint32_t num_children() const { 428 | return has_bound ? 2 : 1; 429 | } 430 | }; 431 | 432 | /// AST node for function type expressions, i.e. `(int32 a, int32)->void`. The first set of children is the parameters, the 433 | /// second set of children are the outputs. 434 | struct AstFunctionTypeExpr { // TODO: handle the fact that parameters are allowed to be completely without name 435 | AstNodeHeader header; 436 | uint16_t num_parameters = 0; 437 | uint16_t num_outputs = 0; 438 | 439 | uint32_t num_children() const { 440 | return num_parameters + num_outputs; 441 | } 442 | }; 443 | 444 | } // namespace cero 445 | -------------------------------------------------------------------------------- /src/cero/syntax/AstToString.cpp: -------------------------------------------------------------------------------- 1 | #include "AstToString.hpp" 2 | 3 | #include "cero/syntax/AstCursor.hpp" 4 | #include "cero/util/Assert.hpp" 5 | 6 | namespace cero { 7 | 8 | namespace { 9 | 10 | std::string_view parameter_specifier_to_string(ParameterSpecifier specifier) { 11 | switch (specifier) { 12 | using enum ParameterSpecifier; 13 | case None: return "value"; 14 | case In: return "in"; 15 | case Var: return "var"; 16 | } 17 | assert_unreachable(); 18 | } 19 | 20 | std::string_view permission_specifier_to_string(PermissionSpecifier spec) { 21 | switch (spec) { 22 | using enum PermissionSpecifier; 23 | case In: return "in"; 24 | case Var: return "var"; 25 | case VarBounded: return "var (bounded)"; 26 | case VarUnbounded: return "var (unbounded)"; 27 | } 28 | assert_unreachable(); 29 | } 30 | 31 | std::string_view binding_specifier_to_string(BindingSpecifier spec) { 32 | switch (spec) { 33 | using enum BindingSpecifier; 34 | case Let: return "let"; 35 | case Var: return "var"; 36 | case Const: return "const"; 37 | case Static: return "static"; 38 | case StaticVar: return "static var"; 39 | } 40 | assert_unreachable(); 41 | } 42 | 43 | std::string_view numeric_literal_kind_to_string(NumericLiteralKind kind) { 44 | switch (kind) { 45 | using enum NumericLiteralKind; 46 | case Decimal: return "decimal"; 47 | case Hexadecimal: return "hexadecimal"; 48 | case Binary: return "binary"; 49 | case Octal: return "octal"; 50 | case Float: return "float"; 51 | case Character: return "character"; 52 | } 53 | assert_unreachable(); 54 | } 55 | 56 | class AstToString : public AstVisitor { 57 | public: 58 | AstToString(const Ast& ast, const SourceGuard& source, uint8_t tab_size) : 59 | string_(fmt::format("AST for {} ({} node{})\n", source.get_name(), ast.num_nodes(), ast.num_nodes() == 1 ? "" : "s")), 60 | cursor_(ast), 61 | source_(source), 62 | tab_size_(tab_size), 63 | edge_(&Body) { 64 | prefixes_.emplace(); 65 | } 66 | 67 | std::string make_string() && { 68 | cursor_.visit_all(*this); 69 | return std::move(string_); 70 | } 71 | 72 | private: 73 | struct Edge { 74 | std::string_view branch; 75 | std::string_view prefix; 76 | }; 77 | 78 | static constexpr Edge Body {"├── ", "│ "}; 79 | static constexpr Edge Tail {"└── ", " "}; 80 | 81 | std::string string_; 82 | AstCursor cursor_; 83 | const SourceGuard& source_; 84 | const uint8_t tab_size_; 85 | 86 | std::stack prefixes_; 87 | const Edge* edge_; 88 | 89 | template 90 | std::string locate(const T& t) const { 91 | return t.header.locate_in(source_, tab_size_).to_short_string(); 92 | } 93 | 94 | void push_level() { 95 | std::string prefix = prefixes_.top(); 96 | prefix.append(edge_->prefix); 97 | prefixes_.push(std::move(prefix)); 98 | } 99 | 100 | void pop_level() { 101 | prefixes_.pop(); 102 | } 103 | 104 | void set_tail(bool at_tail) { 105 | edge_ = at_tail ? &Tail : &Body; 106 | } 107 | 108 | void add_line(std::string_view text) { 109 | string_.append(prefixes_.top()); 110 | string_.append(edge_->branch); 111 | string_.append(text); 112 | string_.append("\n"); 113 | } 114 | 115 | void add_body_line(std::string_view text) { 116 | set_tail(false); 117 | add_line(text); 118 | } 119 | 120 | void add_tail_line(std::string_view text) { 121 | set_tail(true); 122 | add_line(text); 123 | } 124 | 125 | void visit_child_at_body() { 126 | set_tail(false); 127 | visit_child(); 128 | } 129 | 130 | void visit_child_at_tail() { 131 | set_tail(true); 132 | visit_child(); 133 | } 134 | 135 | void visit_child() { 136 | cursor_.visit_child(*this); 137 | } 138 | 139 | void visit_child_if(bool condition) { 140 | if (condition) { 141 | push_level(); 142 | visit_child_at_tail(); 143 | pop_level(); 144 | } 145 | } 146 | 147 | void visit_children(uint32_t n) { 148 | while (n > 0) { 149 | set_tail(--n == 0); 150 | cursor_.visit_child(*this); 151 | } 152 | } 153 | 154 | void visit(const AstRoot& root) override { 155 | visit_children(root.num_definitions); 156 | } 157 | 158 | void visit(const AstStructDefinition& struct_def) override { 159 | add_line(fmt::format("struct `{}`", struct_def.name)); 160 | push_level(); 161 | 162 | pop_level(); 163 | } 164 | 165 | void visit(const AstEnumDefinition& enum_def) override { 166 | add_line(fmt::format("enum `{}`", enum_def.name)); 167 | push_level(); 168 | 169 | pop_level(); 170 | } 171 | 172 | void visit(const AstFunctionDefinition& func_def) override { 173 | add_line(fmt::format("function `{}` {}", func_def.name, locate(func_def))); 174 | push_level(); 175 | 176 | add_body_line("parameters"); 177 | push_level(); 178 | visit_children(func_def.num_parameters); 179 | pop_level(); 180 | 181 | add_body_line("outputs"); 182 | push_level(); 183 | visit_children(func_def.num_outputs); 184 | pop_level(); 185 | 186 | add_tail_line("statements"); 187 | push_level(); 188 | visit_children(func_def.num_statements); 189 | pop_level(); 190 | 191 | pop_level(); 192 | } 193 | 194 | void visit(const AstFunctionParameter& param) override { 195 | auto specifier = parameter_specifier_to_string(param.specifier); 196 | add_line(fmt::format("{} parameter `{}` {}", specifier, param.name, locate(param))); 197 | push_level(); 198 | 199 | set_tail(!param.has_default_argument); 200 | visit_child(); // visit type 201 | if (param.has_default_argument) { 202 | visit_child_at_tail(); 203 | } 204 | 205 | pop_level(); 206 | } 207 | 208 | void visit(const AstFunctionOutput& output) override { 209 | if (output.name.empty()) { 210 | add_line(fmt::format("output {}", locate(output))); 211 | } else { 212 | add_line(fmt::format("output `{}` {}", output.name, locate(output))); 213 | } 214 | 215 | push_level(); 216 | visit_child_at_tail(); 217 | pop_level(); 218 | } 219 | 220 | void visit(const AstBlockStatement& block_stmt) override { 221 | add_line(fmt::format("block {}", locate(block_stmt))); 222 | 223 | push_level(); 224 | visit_children(block_stmt.num_statements); 225 | pop_level(); 226 | } 227 | 228 | void visit(const AstBindingStatement& binding) override { 229 | auto specifier = binding_specifier_to_string(binding.specifier); 230 | add_line(fmt::format("{} binding `{}` {}", specifier, binding.name, locate(binding))); 231 | push_level(); 232 | 233 | if (binding.has_type) { 234 | set_tail(!binding.has_initializer); 235 | add_line("type"); 236 | push_level(); 237 | visit_child_at_tail(); 238 | pop_level(); 239 | } 240 | 241 | if (binding.has_initializer) { 242 | add_tail_line("initializer"); 243 | push_level(); 244 | visit_child_at_tail(); 245 | pop_level(); 246 | } 247 | 248 | pop_level(); 249 | } 250 | 251 | void visit(const AstIfExpr& if_stmt) override { 252 | add_line(fmt::format("if {}", locate(if_stmt))); 253 | push_level(); 254 | 255 | add_body_line("condition"); 256 | push_level(); 257 | visit_child_at_tail(); 258 | pop_level(); 259 | 260 | set_tail(if_stmt.num_else_statements == 0); 261 | add_line("then"); 262 | push_level(); 263 | visit_children(if_stmt.num_then_statements); 264 | pop_level(); 265 | 266 | if (if_stmt.num_else_statements > 0) { 267 | add_tail_line("else"); 268 | push_level(); 269 | visit_children(if_stmt.num_else_statements); 270 | pop_level(); 271 | } 272 | 273 | pop_level(); 274 | } 275 | 276 | void visit(const AstWhileLoop& while_loop) override { 277 | add_line(fmt::format("while {}", locate(while_loop))); 278 | push_level(); 279 | 280 | visit_child_at_body(); 281 | 282 | add_tail_line("statements"); 283 | push_level(); 284 | visit_children(while_loop.num_statements); 285 | pop_level(); 286 | 287 | pop_level(); 288 | } 289 | 290 | void visit(const AstForLoop& for_loop) override { 291 | add_line(fmt::format("for {}", locate(for_loop))); 292 | push_level(); 293 | 294 | visit_child_at_body(); // binding 295 | visit_child_at_body(); // range 296 | 297 | add_tail_line("statements"); 298 | push_level(); 299 | visit_children(for_loop.num_statements); 300 | pop_level(); 301 | 302 | pop_level(); 303 | } 304 | 305 | void visit(const AstNameExpr& name_expr) override { 306 | add_line(fmt::format("name `{}` {}", name_expr.name, locate(name_expr))); 307 | } 308 | 309 | void visit(const AstGenericNameExpr& generic_name_expr) override { 310 | add_line(fmt::format("generic name `{}` {}", generic_name_expr.name, locate(generic_name_expr))); 311 | 312 | push_level(); 313 | visit_children(generic_name_expr.num_generic_args); 314 | pop_level(); 315 | } 316 | 317 | void visit(const AstMemberExpr& member_expr) override { 318 | add_line(fmt::format("member `{}` {}", member_expr.member, locate(member_expr))); 319 | 320 | if (member_expr.num_generic_args > 0) { 321 | add_tail_line("generic arguments"); 322 | push_level(); 323 | visit_children(member_expr.num_generic_args); 324 | pop_level(); 325 | } 326 | } 327 | 328 | void visit(const AstGroupExpr& group_expr) override { 329 | if (group_expr.num_args == 1) { 330 | visit_child(); 331 | return; 332 | } 333 | 334 | add_line(fmt::format("group expression {}", locate(group_expr))); 335 | push_level(); 336 | visit_children(group_expr.num_args); 337 | pop_level(); 338 | } 339 | 340 | void visit(const AstCallExpr& call_expr) override { 341 | add_line(fmt::format("call expression {}", locate(call_expr))); 342 | push_level(); 343 | 344 | visit_child_at_body(); 345 | 346 | add_tail_line("arguments"); 347 | push_level(); 348 | visit_children(call_expr.num_args); 349 | pop_level(); 350 | 351 | pop_level(); 352 | } 353 | 354 | void visit(const AstIndexExpr& index_expr) override { 355 | add_line(fmt::format("index expression {}", locate(index_expr))); 356 | push_level(); 357 | 358 | visit_child_at_body(); 359 | 360 | add_tail_line("arguments"); 361 | push_level(); 362 | visit_children(index_expr.num_args); 363 | pop_level(); 364 | 365 | pop_level(); 366 | } 367 | 368 | void visit(const AstArrayLiteralExpr& array_literal) override { 369 | add_line(fmt::format("array literal {}", locate(array_literal))); 370 | 371 | push_level(); 372 | visit_children(array_literal.num_elements); 373 | pop_level(); 374 | } 375 | 376 | void visit(const AstUnaryExpr& unary_expr) override { 377 | auto op = unary_operator_to_string(unary_expr.op); 378 | add_line(fmt::format("`{}` {}", op, locate(unary_expr))); 379 | push_level(); 380 | 381 | visit_child_at_tail(); 382 | 383 | pop_level(); 384 | } 385 | 386 | void visit(const AstBinaryExpr& binary_expr) override { 387 | auto op = binary_operator_to_string(binary_expr.op); 388 | add_line(fmt::format("`{}` {}", op, locate(binary_expr))); 389 | push_level(); 390 | 391 | visit_child_at_body(); 392 | visit_child_at_tail(); 393 | 394 | pop_level(); 395 | } 396 | 397 | void visit(const AstReturnExpr& return_expr) override { 398 | add_line(fmt::format("return {}", locate(return_expr))); 399 | 400 | push_level(); 401 | visit_children(return_expr.num_expressions); 402 | pop_level(); 403 | } 404 | 405 | void visit(const AstThrowExpr& throw_expr) override { 406 | add_line(fmt::format("throw {}", locate(throw_expr))); 407 | visit_child_if(throw_expr.has_expression); 408 | } 409 | 410 | void visit(const AstBreakExpr& break_expr) override { 411 | add_line(fmt::format("break {}", locate(break_expr))); 412 | visit_child_if(break_expr.has_label); 413 | } 414 | 415 | void visit(const AstContinueExpr& continue_expr) override { 416 | add_line(fmt::format("continue {}", locate(continue_expr))); 417 | visit_child_if(continue_expr.has_label); 418 | } 419 | 420 | void visit(const AstNumericLiteralExpr& numeric_literal) override { 421 | auto kind = numeric_literal_kind_to_string(numeric_literal.kind); 422 | add_line(fmt::format("{} literal `{}` {}", kind, " ---TODO--- ", locate(numeric_literal))); // TODO: add number 423 | } 424 | 425 | void visit(const AstStringLiteralExpr& string_literal) override { 426 | add_line(fmt::format("string literal `{}` {}", string_literal.value, locate(string_literal))); 427 | } 428 | 429 | void visit(const AstPermissionExpr& permission) override { 430 | auto specifier = permission_specifier_to_string(permission.specifier); 431 | add_line(fmt::format("permission `{}` {}", specifier, locate(permission))); 432 | 433 | push_level(); 434 | visit_children(permission.num_args); 435 | pop_level(); 436 | } 437 | 438 | void visit(const AstPointerTypeExpr& ptr_type) override { 439 | add_line(fmt::format("pointer type {}", locate(ptr_type))); 440 | push_level(); 441 | 442 | if (ptr_type.has_permission) { 443 | add_body_line("permission"); 444 | push_level(); 445 | visit_child_at_tail(); 446 | pop_level(); 447 | } 448 | 449 | add_tail_line("type"); 450 | push_level(); 451 | visit_child_at_tail(); 452 | pop_level(); 453 | 454 | pop_level(); 455 | } 456 | 457 | void visit(const AstArrayTypeExpr& array_type) override { 458 | add_line(fmt::format("array type {}", locate(array_type))); 459 | push_level(); 460 | 461 | if (array_type.has_bound) { 462 | add_body_line("bound"); 463 | push_level(); 464 | visit_child_at_tail(); 465 | pop_level(); 466 | } 467 | 468 | add_tail_line("element type"); 469 | push_level(); 470 | visit_child_at_tail(); 471 | pop_level(); 472 | 473 | pop_level(); 474 | } 475 | 476 | void visit(const AstFunctionTypeExpr& func_type) override { 477 | add_line(fmt::format("function type {}", locate(func_type))); 478 | push_level(); 479 | 480 | add_body_line("parameters"); 481 | push_level(); 482 | visit_children(func_type.num_parameters); 483 | pop_level(); 484 | 485 | add_tail_line("outputs"); 486 | push_level(); 487 | visit_children(func_type.num_outputs); 488 | pop_level(); 489 | 490 | pop_level(); 491 | } 492 | }; 493 | 494 | } // namespace 495 | 496 | std::string ast_to_string(const Ast& ast, const SourceGuard& source, uint8_t tab_size) { 497 | return AstToString(ast, source, tab_size).make_string(); 498 | } 499 | 500 | } // namespace cero 501 | -------------------------------------------------------------------------------- /src/cero/syntax/AstToString.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Ast.hpp" 4 | #include "cero/syntax/Source.hpp" 5 | 6 | namespace cero { 7 | 8 | std::string ast_to_string(const Ast& ast, const SourceGuard& source, uint8_t tab_size); 9 | 10 | } // namespace cero 11 | -------------------------------------------------------------------------------- /src/cero/syntax/AstVisitor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/AstNode.hpp" 4 | 5 | namespace cero { 6 | 7 | class AstVisitor { 8 | public: 9 | virtual ~AstVisitor() = default; 10 | 11 | virtual void visit(const AstRoot& root) = 0; 12 | virtual void visit(const AstStructDefinition& struct_def) = 0; 13 | virtual void visit(const AstEnumDefinition& enum_def) = 0; 14 | virtual void visit(const AstFunctionDefinition& func_def) = 0; 15 | virtual void visit(const AstFunctionParameter& param) = 0; 16 | virtual void visit(const AstFunctionOutput& output) = 0; 17 | virtual void visit(const AstBlockStatement& block_stmt) = 0; 18 | virtual void visit(const AstBindingStatement& binding) = 0; 19 | virtual void visit(const AstIfExpr& if_stmt) = 0; 20 | virtual void visit(const AstWhileLoop& while_loop) = 0; 21 | virtual void visit(const AstForLoop& for_loop) = 0; 22 | virtual void visit(const AstNameExpr& name_expr) = 0; 23 | virtual void visit(const AstGenericNameExpr& generic_name_expr) = 0; 24 | virtual void visit(const AstMemberExpr& member_expr) = 0; 25 | virtual void visit(const AstGroupExpr& group_expr) = 0; 26 | virtual void visit(const AstCallExpr& call_expr) = 0; 27 | virtual void visit(const AstIndexExpr& index_expr) = 0; 28 | virtual void visit(const AstArrayLiteralExpr& array_literal) = 0; 29 | virtual void visit(const AstUnaryExpr& unary_expr) = 0; 30 | virtual void visit(const AstBinaryExpr& binary_expr) = 0; 31 | virtual void visit(const AstReturnExpr& return_expr) = 0; 32 | virtual void visit(const AstThrowExpr& throw_expr) = 0; 33 | virtual void visit(const AstBreakExpr& break_expr) = 0; 34 | virtual void visit(const AstContinueExpr& continue_expr) = 0; 35 | virtual void visit(const AstNumericLiteralExpr& numeric_literal) = 0; 36 | virtual void visit(const AstStringLiteralExpr& string_literal) = 0; 37 | virtual void visit(const AstPermissionExpr& permission) = 0; 38 | virtual void visit(const AstPointerTypeExpr& ptr_type) = 0; 39 | virtual void visit(const AstArrayTypeExpr& array_type) = 0; 40 | virtual void visit(const AstFunctionTypeExpr& func_type) = 0; 41 | }; 42 | 43 | } // namespace cero 44 | -------------------------------------------------------------------------------- /src/cero/syntax/Encoding.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | inline bool is_dec_digit(char c) { 6 | return c >= '0' && c <= '9'; 7 | } 8 | 9 | inline bool is_hex_digit(char c) { 10 | return is_dec_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); 11 | } 12 | 13 | inline bool is_standard_ascii(char c) { 14 | return static_cast(c) >> 7 == 0; 15 | } 16 | 17 | inline bool is_ascii_word_character(char c) { 18 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; 19 | } 20 | 21 | inline bool is_whitespace(char c) { 22 | return c == ' ' || (c >= '\t' && c <= '\r'); 23 | } 24 | 25 | bool is_utf8_xid_start(uint32_t encoded); 26 | bool is_utf8_xid_continue(uint32_t encoded); 27 | 28 | } // namespace cero 29 | -------------------------------------------------------------------------------- /src/cero/syntax/Lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "Lexer.hpp" 2 | 3 | #include "cero/report/Message.hpp" 4 | #include "cero/report/MessageArgs.hpp" 5 | #include "cero/syntax/Encoding.hpp" 6 | #include "cero/syntax/SourceCursor.hpp" 7 | 8 | namespace cero { 9 | 10 | class Lexer { 11 | public: 12 | Lexer(const SourceGuard& source, Reporter& reporter, bool emit_comments, uint8_t tab_size) : 13 | stream_(source), 14 | cursor_(source), 15 | source_(source), 16 | reporter_(reporter), 17 | emit_comments_(emit_comments), 18 | tab_size_(tab_size) { 19 | } 20 | 21 | TokenStream lex() && { 22 | if (source_.get_length() > MaxSourceLength) { 23 | const CodeLocation blank {source_.get_name()}; 24 | reporter_.report(blank, Message::SourceFileTooLarge, MessageArgs(MaxSourceLength)); 25 | } else { 26 | lex_source(); 27 | } 28 | 29 | stream_.add_token(TokenKind::EndOfFile, cursor_.offset()); 30 | return std::move(stream_); 31 | } 32 | 33 | private: 34 | TokenStream stream_; 35 | SourceCursor cursor_; 36 | const SourceGuard& source_; 37 | Reporter& reporter_; 38 | const bool emit_comments_; 39 | const uint8_t tab_size_; 40 | 41 | void lex_source() { 42 | while (auto next = cursor_.peek()) { 43 | const char character = *next; 44 | if (is_whitespace(character)) { 45 | cursor_.advance(); 46 | continue; 47 | } 48 | 49 | const auto offset = cursor_.offset(); 50 | cursor_.advance(); 51 | switch (character) { 52 | // clang-format off 53 | case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': 54 | case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': 55 | case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': 56 | 57 | case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': 58 | case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': 59 | case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': 60 | lex_word(offset); break; 61 | // clang-format on 62 | 63 | case '0': lex_zero(offset); break; 64 | case '1': 65 | case '2': 66 | case '3': 67 | case '4': 68 | case '5': 69 | case '6': 70 | case '7': 71 | case '8': 72 | case '9': lex_number(offset); break; 73 | 74 | case '(': stream_.add_token(TokenKind::LParen, offset); break; 75 | case ')': stream_.add_token(TokenKind::RParen, offset); break; 76 | case '[': stream_.add_token(TokenKind::LBracket, offset); break; 77 | case ']': stream_.add_token(TokenKind::RBracket, offset); break; 78 | case '{': stream_.add_token(TokenKind::LBrace, offset); break; 79 | case '}': stream_.add_token(TokenKind::RBrace, offset); break; 80 | case ',': stream_.add_token(TokenKind::Comma, offset); break; 81 | case ';': stream_.add_token(TokenKind::Semicolon, offset); break; 82 | case '^': stream_.add_token(TokenKind::Caret, offset); break; 83 | case '?': stream_.add_token(TokenKind::Quest, offset); break; 84 | case '@': stream_.add_token(TokenKind::At, offset); break; 85 | case '#': stream_.add_token(TokenKind::Hash, offset); break; 86 | case '$': stream_.add_token(TokenKind::Dollar, offset); break; 87 | case '+': stream_.add_token(lex_plus(), offset); break; 88 | case '-': stream_.add_token(lex_minus(), offset); break; 89 | case '*': stream_.add_token(lex_star(), offset); break; 90 | case '/': lex_slash(offset); break; 91 | case '%': stream_.add_token(lex_percent(), offset); break; 92 | case '&': stream_.add_token(lex_ampersand(), offset); break; 93 | case '|': stream_.add_token(lex_pipe(), offset); break; 94 | case '~': stream_.add_token(lex_tilde(), offset); break; 95 | case '!': stream_.add_token(lex_bang(), offset); break; 96 | case ':': stream_.add_token(lex_colon(), offset); break; 97 | case '=': stream_.add_token(lex_equal(), offset); break; 98 | case '<': stream_.add_token(lex_left_angle(), offset); break; 99 | case '>': lex_right_angle(offset); break; 100 | case '.': lex_dot(offset); break; 101 | case '"': lex_quoted_sequence(TokenKind::StringLiteral, offset, '"'); break; 102 | case '\'': lex_quoted_sequence(TokenKind::CharLiteral, offset, '\''); break; 103 | default: lex_unicode_name(character, offset); break; 104 | } 105 | } 106 | } 107 | 108 | void lex_word(SourceOffset offset) { 109 | eat_word_token_rest(); 110 | 111 | auto length = cursor_.offset() - offset; 112 | auto lexeme = source_.get_text().substr(offset, length); 113 | auto kind = identify_keyword(lexeme); 114 | 115 | stream_.add_token(kind, offset); 116 | } 117 | 118 | void eat_word_token_rest() { 119 | while (auto next = cursor_.peek()) { 120 | const char c = *next; 121 | if (is_standard_ascii(c)) { 122 | if (!is_ascii_word_character(c)) { 123 | break; 124 | } 125 | } else { 126 | if (!check_multibyte_utf8_value(c, cursor_.offset())) { 127 | break; 128 | } 129 | } 130 | cursor_.advance(); 131 | } 132 | } 133 | 134 | template 135 | bool check_multibyte_utf8_value(char character, SourceOffset offset) { 136 | const auto leading_byte = static_cast(character); 137 | const auto leading_ones = static_cast(std::countl_one(leading_byte)); 138 | 139 | uint32_t encoded_value = leading_byte; 140 | if (leading_ones >= 2 && leading_ones <= 4) { 141 | auto bytes = std::bit_cast>(encoded_value); 142 | for (uint32_t i = 1; i != leading_ones; ++i) { 143 | bytes[i] = cursor_.next().value_or('\0'); 144 | } 145 | 146 | encoded_value = std::bit_cast(bytes); 147 | if (Utf8Predicate(encoded_value)) { 148 | return true; 149 | } 150 | } 151 | 152 | report(Message::InvalidCharacter, offset, MessageArgs(encoded_value)); 153 | return false; 154 | } 155 | 156 | void lex_zero(SourceOffset offset) { 157 | if (cursor_.match('x')) { 158 | eat_number_literal(); 159 | stream_.add_token(TokenKind::HexIntLiteral, offset); 160 | } else if (cursor_.match('b')) { 161 | eat_number_literal(); // consume any decimal digit for better errors during literal parsing later 162 | stream_.add_token(TokenKind::BinIntLiteral, offset); 163 | } else if (cursor_.match('o')) { 164 | eat_number_literal(); // consume any decimal digit for better errors during literal parsing later 165 | stream_.add_token(TokenKind::OctIntLiteral, offset); 166 | } else { 167 | lex_number(offset); 168 | } 169 | } 170 | 171 | void lex_number(SourceOffset offset) { 172 | eat_number_literal(); 173 | auto cursor_at_token_end = cursor_; 174 | 175 | while (auto c = cursor_.peek()) { 176 | if (!is_whitespace(*c)) { 177 | break; 178 | } 179 | cursor_.advance(); 180 | } 181 | 182 | auto cursor_at_dot = cursor_; 183 | 184 | // check for rational part 185 | char next = cursor_.next().value_or('\0'); 186 | if (next == '.') { 187 | const bool matched_number = eat_decimal_number(); 188 | if (matched_number) { 189 | stream_.add_token(TokenKind::FloatLiteral, offset); 190 | return; 191 | } else { 192 | cursor_ = cursor_at_dot; // reset to dot if there's no fractional part 193 | } 194 | } else { 195 | cursor_ = cursor_at_token_end; 196 | } 197 | 198 | stream_.add_token(TokenKind::DecIntLiteral, offset); 199 | } 200 | 201 | template 202 | void eat_number_literal() { 203 | while (auto next = cursor_.peek()) { 204 | const char c = *next; 205 | if (!CharPredicate(c) && !is_whitespace(c)) { 206 | break; 207 | } 208 | cursor_.advance(); 209 | } 210 | } 211 | 212 | bool eat_decimal_number() { 213 | bool matched = false; 214 | auto lookahead = cursor_; 215 | 216 | while (auto next = lookahead.peek()) { 217 | const char c = *next; 218 | 219 | if (is_dec_digit(c)) { 220 | cursor_ = lookahead; 221 | cursor_.advance(); 222 | matched = true; 223 | } else if (!is_whitespace(c)) { 224 | break; 225 | } 226 | 227 | lookahead.advance(); 228 | } 229 | return matched; 230 | } 231 | 232 | void lex_dot(SourceOffset offset) { 233 | if (cursor_.match('.')) { 234 | if (cursor_.match('.')) { 235 | stream_.add_token(TokenKind::Ellipsis, offset); 236 | } else { 237 | stream_.add_token(TokenKind::Dot, offset); 238 | stream_.add_token(TokenKind::Dot, offset + 1); 239 | } 240 | } else if (is_dec_digit(cursor_.peek().value_or('\0'))) { 241 | eat_number_literal(); 242 | stream_.add_token(TokenKind::FloatLiteral, offset); 243 | } else { 244 | stream_.add_token(TokenKind::Dot, offset); 245 | } 246 | } 247 | 248 | TokenKind lex_colon() { 249 | if (cursor_.match(':')) { 250 | return TokenKind::ColonColon; 251 | } else { 252 | return TokenKind::Colon; 253 | } 254 | } 255 | 256 | TokenKind lex_left_angle() { 257 | if (cursor_.match('<')) { 258 | if (cursor_.match('=')) { 259 | return TokenKind::LAngleLAngleEq; 260 | } else { 261 | return TokenKind::LAngleLAngle; 262 | } 263 | } else if (cursor_.match('=')) { 264 | return TokenKind::LAngleEq; 265 | } else { 266 | return TokenKind::LAngle; 267 | } 268 | } 269 | 270 | void lex_right_angle(SourceOffset offset) { 271 | if (cursor_.match('>')) { 272 | if (cursor_.match('=')) { 273 | stream_.add_token(TokenKind::RAngleRAngleEq, offset); 274 | } else { 275 | stream_.add_token(TokenKind::RAngle, offset); 276 | stream_.add_token(TokenKind::RAngle, offset + 1); 277 | } 278 | } else if (cursor_.match('=')) { 279 | stream_.add_token(TokenKind::RAngleEq, offset); 280 | } else { 281 | stream_.add_token(TokenKind::RAngle, offset); 282 | } 283 | } 284 | 285 | TokenKind lex_equal() { 286 | if (cursor_.match('=')) { 287 | return TokenKind::EqEq; 288 | } else if (cursor_.match('>')) { 289 | return TokenKind::ThickArrow; 290 | } else { 291 | return TokenKind::Eq; 292 | } 293 | } 294 | 295 | TokenKind lex_plus() { 296 | if (cursor_.match('+')) { 297 | return TokenKind::PlusPlus; 298 | } else if (cursor_.match('=')) { 299 | return TokenKind::PlusEq; 300 | } else { 301 | return TokenKind::Plus; 302 | } 303 | } 304 | 305 | TokenKind lex_minus() { 306 | if (cursor_.match('>')) { 307 | return TokenKind::ThinArrow; 308 | } else if (cursor_.match('-')) { 309 | return TokenKind::MinusMinus; 310 | } else if (cursor_.match('=')) { 311 | return TokenKind::MinusEq; 312 | } else { 313 | return TokenKind::Minus; 314 | } 315 | } 316 | 317 | TokenKind lex_star() { 318 | if (cursor_.match('*')) { 319 | if (cursor_.match('=')) { 320 | return TokenKind::StarStarEq; 321 | } else { 322 | return TokenKind::StarStar; 323 | } 324 | } else if (cursor_.match('=')) { 325 | return TokenKind::StarEq; 326 | } else { 327 | return TokenKind::Star; 328 | } 329 | } 330 | 331 | void lex_slash(SourceOffset offset) { 332 | if (cursor_.match('/')) { 333 | eat_line_comment(); 334 | if (emit_comments_) { 335 | stream_.add_token(TokenKind::LineComment, offset); 336 | } 337 | } else if (cursor_.match('*')) { 338 | eat_block_comment(offset); 339 | if (emit_comments_) { 340 | stream_.add_token(TokenKind::BlockComment, offset); 341 | } 342 | } else if (cursor_.match('=')) { 343 | stream_.add_token(TokenKind::SlashEq, offset); 344 | } else { 345 | stream_.add_token(TokenKind::Slash, offset); 346 | } 347 | } 348 | 349 | void eat_line_comment() { 350 | while (auto next = cursor_.peek()) { 351 | if (*next == '\n') { 352 | break; 353 | } 354 | cursor_.advance(); 355 | } 356 | } 357 | 358 | void eat_block_comment(SourceOffset offset) { 359 | uint32_t unclosed_count = 1; 360 | while (cursor_.valid()) { 361 | if (cursor_.match('*')) { 362 | if (cursor_.match('/') && --unclosed_count == 0) { 363 | return; 364 | } 365 | } else if (cursor_.match('/')) { 366 | if (cursor_.match('*')) { 367 | ++unclosed_count; 368 | } 369 | } else { 370 | cursor_.advance(); 371 | } 372 | } 373 | 374 | report(Message::UnterminatedBlockComment, offset, MessageArgs()); 375 | } 376 | 377 | TokenKind lex_percent() { 378 | if (cursor_.match('=')) { 379 | return TokenKind::PercentEq; 380 | } else { 381 | return TokenKind::Percent; 382 | } 383 | } 384 | 385 | TokenKind lex_bang() { 386 | if (cursor_.match('=')) { 387 | return TokenKind::BangEq; 388 | } else { 389 | return TokenKind::Bang; 390 | } 391 | } 392 | 393 | TokenKind lex_ampersand() { 394 | if (cursor_.match('&')) { 395 | if (cursor_.match('=')) { 396 | return TokenKind::AmpAmpEq; 397 | } else { 398 | return TokenKind::AmpAmp; 399 | } 400 | } else if (cursor_.match('=')) { 401 | return TokenKind::AmpEq; 402 | } else { 403 | return TokenKind::Amp; 404 | } 405 | } 406 | 407 | TokenKind lex_pipe() { 408 | if (cursor_.match('|')) { 409 | if (cursor_.match('=')) { 410 | return TokenKind::PipePipeEq; 411 | } else { 412 | return TokenKind::PipePipe; 413 | } 414 | } else if (cursor_.match('=')) { 415 | return TokenKind::PipeEq; 416 | } else { 417 | return TokenKind::Pipe; 418 | } 419 | } 420 | 421 | TokenKind lex_tilde() { 422 | if (cursor_.match('=')) { 423 | return TokenKind::TildeEq; 424 | } else { 425 | return TokenKind::Tilde; 426 | } 427 | } 428 | 429 | void lex_quoted_sequence(TokenKind kind, SourceOffset offset, char quote) { 430 | bool ignore_quote = false; 431 | while (auto next = cursor_.peek()) { 432 | const char c = *next; 433 | if (c == '\n') { 434 | report(Message::MissingClosingQuote, cursor_.offset(), MessageArgs()); 435 | break; 436 | } 437 | 438 | cursor_.advance(); 439 | 440 | if (c == '\\') { 441 | ignore_quote ^= true; // bool gets flipped so we correctly handle an escaped backslash within the literal 442 | } else if (c == quote && !ignore_quote) { 443 | break; 444 | } else if (ignore_quote) { 445 | ignore_quote = false; 446 | } 447 | } 448 | 449 | stream_.add_token(kind, offset); 450 | } 451 | 452 | void lex_unicode_name(char character, SourceOffset offset) { 453 | if (check_multibyte_utf8_value(character, offset)) { 454 | eat_word_token_rest(); 455 | } 456 | stream_.add_token(TokenKind::Name, offset); 457 | } 458 | 459 | void report(Message message, SourceOffset offset, MessageArgs args) { 460 | auto location = source_.locate(offset, tab_size_); 461 | reporter_.report(location, message, std::move(args)); 462 | stream_.has_errors_ = true; 463 | } 464 | 465 | static TokenKind identify_keyword(std::string_view lexeme) { 466 | using enum TokenKind; 467 | switch (lexeme.length()) { // clang-format off 468 | case 2: 469 | if (lexeme == "do") return Do; 470 | if (lexeme == "if") return If; 471 | if (lexeme == "in") return In; 472 | break; 473 | 474 | case 3: 475 | if (lexeme == "for") return For; 476 | if (lexeme == "let") return Let; 477 | if (lexeme == "try") return Try; 478 | if (lexeme == "var") return Var; 479 | break; 480 | 481 | case 4: 482 | if (lexeme == "else") return Else; 483 | if (lexeme == "enum") return Enum; 484 | break; 485 | 486 | case 5: 487 | if (lexeme == "break") return Break; 488 | if (lexeme == "catch") return Catch; 489 | if (lexeme == "const") return Const; 490 | if (lexeme == "throw") return Throw; 491 | if (lexeme == "while") return While; 492 | break; 493 | 494 | case 6: 495 | if (lexeme == "public") return Public; 496 | if (lexeme == "return") return Return; 497 | if (lexeme == "static") return Static; 498 | if (lexeme == "struct") return Struct; 499 | if (lexeme == "switch") return Switch; 500 | break; 501 | 502 | case 7: 503 | if (lexeme == "private") return Private; 504 | break; 505 | 506 | case 8: 507 | if (lexeme == "continue") return Continue; 508 | break; 509 | 510 | case 9: 511 | if (lexeme == "unchecked") return Unchecked; 512 | break; 513 | } // clang-format on 514 | return Name; 515 | } 516 | }; 517 | 518 | TokenStream run_lexer(const SourceGuard& source, Reporter& reporter, bool emit_comments, uint8_t tab_size) { 519 | return Lexer(source, reporter, emit_comments, tab_size).lex(); 520 | } 521 | 522 | } // namespace cero 523 | -------------------------------------------------------------------------------- /src/cero/syntax/Lexer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/report/Reporter.hpp" 4 | #include "cero/syntax/Source.hpp" 5 | #include "cero/syntax/TokenStream.hpp" 6 | 7 | namespace cero { 8 | 9 | /// Performs lexical analysis on a single source input. 10 | TokenStream run_lexer(const SourceGuard& source, Reporter& reporter, bool emit_comments, uint8_t tab_size); 11 | 12 | } // namespace cero 13 | -------------------------------------------------------------------------------- /src/cero/syntax/Literal.cpp: -------------------------------------------------------------------------------- 1 | #include "Literal.hpp" 2 | 3 | namespace cero { 4 | 5 | void evaluate_dec_int_literal(std::string_view) { 6 | } 7 | 8 | void evaluate_hex_int_literal(std::string_view) { 9 | } 10 | 11 | void evaluate_bin_int_literal(std::string_view) { 12 | } 13 | 14 | void evaluate_oct_int_literal(std::string_view) { 15 | } 16 | 17 | void evaluate_float_literal(std::string_view) { 18 | } 19 | 20 | void evaluate_char_literal(std::string_view) { 21 | } 22 | 23 | std::string evaluate_string_literal(std::string_view lexeme) { 24 | // TODO: escape sequences 25 | return std::string(lexeme); 26 | } 27 | 28 | } // namespace cero 29 | -------------------------------------------------------------------------------- /src/cero/syntax/Literal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | void evaluate_dec_int_literal(std::string_view lexeme); 6 | void evaluate_hex_int_literal(std::string_view lexeme); 7 | void evaluate_bin_int_literal(std::string_view lexeme); 8 | void evaluate_oct_int_literal(std::string_view lexeme); 9 | void evaluate_float_literal(std::string_view lexeme); 10 | void evaluate_char_literal(std::string_view lexeme); 11 | std::string evaluate_string_literal(std::string_view lexeme); 12 | 13 | } // namespace cero 14 | -------------------------------------------------------------------------------- /src/cero/syntax/Parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/report/Reporter.hpp" 4 | #include "cero/syntax/Ast.hpp" 5 | #include "cero/syntax/Source.hpp" 6 | #include "cero/syntax/TokenStream.hpp" 7 | 8 | namespace cero { 9 | 10 | Ast run_parser(const TokenStream& token_stream, const SourceGuard& source, Reporter& reporter, uint8_t tab_size); 11 | 12 | Ast run_parser_on_source(const SourceGuard& source, Reporter& reporter, uint8_t tab_size); 13 | 14 | } // namespace cero 15 | -------------------------------------------------------------------------------- /src/cero/syntax/Source.cpp: -------------------------------------------------------------------------------- 1 | #include "Source.hpp" 2 | 3 | namespace cero { 4 | 5 | std::string_view SourceGuard::get_text() const { 6 | return _source_text; 7 | } 8 | 9 | size_t SourceGuard::get_length() const { 10 | return _source_text.length(); 11 | } 12 | 13 | std::string_view SourceGuard::get_name() const { 14 | return _name; 15 | } 16 | 17 | CodeLocation SourceGuard::locate(SourceOffset offset, uint8_t tab_size) const { 18 | auto range = _source_text.substr(0, offset); 19 | 20 | size_t last_line = std::string_view::npos; 21 | uint32_t line = 1; 22 | for (size_t i = 0; i < range.size(); ++i) { 23 | if (range[i] == '\n') { 24 | ++line; 25 | last_line = i; 26 | } 27 | } 28 | 29 | range = range.substr(last_line + 1); 30 | 31 | uint32_t column = 1; 32 | for (char c : range) { 33 | column += c == '\t' ? tab_size : 1; 34 | } 35 | return { 36 | .source_name = _name, 37 | .line = line, 38 | .column = column, 39 | }; 40 | } 41 | 42 | SourceGuard::SourceGuard(std::string_view source_text, std::string_view name) : 43 | _mapping(std::nullopt), 44 | _source_text(source_text), 45 | _name(name) { 46 | } 47 | 48 | SourceGuard::SourceGuard(FileMapping&& mapping, std::string_view name) : 49 | _mapping(std::move(mapping)), 50 | _source_text(_mapping->get_text()), 51 | _name(name) { 52 | } 53 | 54 | Source Source::from_file(std::string_view path) { 55 | return Source(path, {}); 56 | } 57 | 58 | Source Source::from_string(std::string_view name, std::string_view source_text) { 59 | return Source(name, source_text); 60 | } 61 | 62 | std::expected Source::lock() const { 63 | if (_source_text.data() == nullptr) { 64 | return FileMapping::from(_name).transform([&](FileMapping&& file_mapping) { 65 | return SourceGuard(std::move(file_mapping), _name); 66 | }); 67 | } else { 68 | return SourceGuard(_source_text, _name); 69 | } 70 | } 71 | 72 | std::string_view Source::get_name() const { 73 | return _name; 74 | } 75 | 76 | Source::Source(std::string_view name, std::string_view source_text) : 77 | _name(name), 78 | _source_text(source_text) { 79 | } 80 | 81 | } // namespace cero 82 | -------------------------------------------------------------------------------- /src/cero/syntax/Source.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/report/CodeLocation.hpp" 4 | #include "cero/util/FileMapping.hpp" 5 | 6 | namespace cero { 7 | 8 | /// Recommended type to use for values representing offsets into source code. 9 | using SourceOffset = uint32_t; 10 | 11 | /// Allows access to source code for processing, possibly from a memory-mapped file. Closes the memory-mapped file when going 12 | /// out of scope. It is safe for this object to outlive the source it came from. 13 | class SourceGuard { 14 | public: 15 | /// Gets a view of the source code. 16 | std::string_view get_text() const; 17 | 18 | /// Gets the length of the source input. 19 | size_t get_length() const; 20 | 21 | /// Gets the path of the original source input. 22 | std::string_view get_name() const; 23 | 24 | /// Gets the line and column that a given source offset corresponds to. 25 | CodeLocation locate(SourceOffset offset, uint8_t tab_size) const; 26 | 27 | private: 28 | std::optional _mapping; 29 | std::string_view _source_text; 30 | std::string_view _name; 31 | 32 | SourceGuard(std::string_view source_text, std::string_view name); 33 | SourceGuard(FileMapping&& mapping, std::string_view name); 34 | 35 | friend class Source; 36 | }; 37 | 38 | /// Represents a source input for the compiler, either originating from a file or from a given string of source code. Accessing 39 | /// the source code requires using the lock method. 40 | class Source { 41 | public: 42 | /// Creates a source representing a source file at the given path. 43 | static Source from_file(std::string_view path); 44 | 45 | /// Creates a source directly from a string containing source code. Useful for testing so that there doesn't have to be an 46 | /// extra source file for a test. 47 | /// @param name Used as the path instead of an actual file path. 48 | /// @param source_text Used as the content of the source. 49 | static Source from_string(std::string_view name, std::string_view source_text); 50 | 51 | /// If the source represents a file, tries to open it as a memory-mapped file that will be closed when the guard goes out of 52 | /// scope. If the operation fails, the system error code is returned. Locking source objects created directly from strings 53 | /// will never fail. 54 | std::expected lock() const; 55 | 56 | /// Gets the path of the source input, or the name given to it. 57 | std::string_view get_name() const; 58 | 59 | private: 60 | std::string_view _name; 61 | std::string_view _source_text; 62 | 63 | Source(std::string_view name, std::string_view source_text); 64 | }; 65 | 66 | /// Minimum number of bits required to represent any offset into a Cero source file. It is 24 bits so that an offset value can 67 | /// fit into a 32-bit integer, leaving 8 bits available for metadata serving various purposes. 68 | constexpr inline size_t SourceOffsetBits = 24; 69 | 70 | /// Maximum allowed size of a Cero source file in bytes (circa 16 MiB). 71 | constexpr inline SourceOffset MaxSourceLength = (1 << SourceOffsetBits) - 1; 72 | 73 | } // namespace cero 74 | -------------------------------------------------------------------------------- /src/cero/syntax/SourceCursor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Source.hpp" 4 | 5 | namespace cero { 6 | 7 | class SourceCursor { 8 | public: 9 | /// Creates a cursor positioned at the start of the given source. 10 | explicit SourceCursor(const SourceGuard& source) : 11 | it_(source.get_text().begin()), 12 | begin_(it_), 13 | end_(source.get_text().end()) { 14 | } 15 | 16 | /// Returns the current character and then advances, or returns null if the cursor is at the end. 17 | std::optional next() { 18 | if (it_ != end_) { 19 | return *it_++; 20 | } else { 21 | return std::nullopt; 22 | } 23 | } 24 | 25 | /// Returns the current character or null if the cursor is at the end. 26 | std::optional peek() const { 27 | if (it_ != end_) { 28 | return *it_; 29 | } else { 30 | return std::nullopt; 31 | } 32 | } 33 | 34 | /// Moves cursor to the next character. 35 | void advance() { 36 | if (it_ != end_) { 37 | ++it_; 38 | } 39 | } 40 | 41 | /// Returns true and advances if the current character equals the expected, otherwise false. 42 | bool match(char expected) { 43 | if (it_ != end_ && *it_ == expected) { 44 | ++it_; 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | } 50 | 51 | /// True if the cursor is not at the end. 52 | bool valid() const { 53 | return it_ != end_; 54 | } 55 | 56 | /// Current offset from the beginning of the source text. 57 | SourceOffset offset() const { 58 | return static_cast(it_ - begin_); 59 | } 60 | 61 | private: 62 | std::string_view::iterator it_; 63 | std::string_view::iterator begin_; 64 | std::string_view::iterator end_; 65 | }; 66 | 67 | } // namespace cero 68 | -------------------------------------------------------------------------------- /src/cero/syntax/Token.cpp: -------------------------------------------------------------------------------- 1 | #include "Token.hpp" 2 | 3 | #include "cero/syntax/SourceCursor.hpp" 4 | #include "cero/util/Assert.hpp" 5 | 6 | namespace cero { 7 | 8 | std::string_view token_kind_to_string(TokenKind kind) { 9 | using enum TokenKind; 10 | switch (kind) { 11 | case Name: return "Name"; 12 | case LineComment: return "LineComment"; 13 | case BlockComment: return "BlockComment"; 14 | case DecIntLiteral: return "DecIntLiteral"; 15 | case HexIntLiteral: return "HexIntLiteral"; 16 | case BinIntLiteral: return "BinIntLiteral"; 17 | case OctIntLiteral: return "OctIntLiteral"; 18 | case FloatLiteral: return "FloatLiteral"; 19 | case CharLiteral: return "CharLiteral"; 20 | case StringLiteral: return "StringLiteral"; 21 | case Dot: return "Dot"; 22 | case Comma: return "Comma"; 23 | case Colon: return "Colon"; 24 | case Semicolon: return "Semicolon"; 25 | case LBrace: return "LBrace"; 26 | case RBrace: return "RBrace"; 27 | case LParen: return "LParen"; 28 | case RParen: return "RParen"; 29 | case LBracket: return "LBracket"; 30 | case RBracket: return "RBracket"; 31 | case LAngle: return "LAngle"; 32 | case RAngle: return "RAngle"; 33 | case Eq: return "Eq"; 34 | case Plus: return "Plus"; 35 | case Minus: return "Minus"; 36 | case Star: return "Star"; 37 | case Slash: return "Slash"; 38 | case Percent: return "Percent"; 39 | case Amp: return "Amp"; 40 | case Pipe: return "Pipe"; 41 | case Tilde: return "Tilde"; 42 | case Caret: return "Caret"; 43 | case Bang: return "Bang"; 44 | case Quest: return "Quest"; 45 | case At: return "At"; 46 | case Dollar: return "Dollar"; 47 | case Hash: return "Hash"; 48 | case ThinArrow: return "ThinArrow"; 49 | case ThickArrow: return "ThickArrow"; 50 | case ColonColon: return "ColonColon"; 51 | case PlusPlus: return "PlusPlus"; 52 | case MinusMinus: return "MinusMinus"; 53 | case StarStar: return "StarStar"; 54 | case LAngleLAngle: return "LAngleLAngle"; 55 | case AmpAmp: return "AmpAmp"; 56 | case PipePipe: return "PipePipe"; 57 | case EqEq: return "EqEq"; 58 | case BangEq: return "BangEq"; 59 | case LAngleEq: return "LAngleEq"; 60 | case RAngleEq: return "RAngleEq"; 61 | case PlusEq: return "PlusEq"; 62 | case MinusEq: return "MinusEq"; 63 | case StarEq: return "StarEq"; 64 | case SlashEq: return "SlashEq"; 65 | case PercentEq: return "PercentEq"; 66 | case AmpEq: return "AmpEq"; 67 | case PipeEq: return "PipeEq"; 68 | case TildeEq: return "TildeEq"; 69 | case Ellipsis: return "Ellipsis"; 70 | case StarStarEq: return "StarStarEq"; 71 | case LAngleLAngleEq: return "LAngleLAngleEq"; 72 | case RAngleRAngleEq: return "RAngleRAngleEq"; 73 | case AmpAmpEq: return "AmpAmpEq"; 74 | case PipePipeEq: return "PipePipeEq"; 75 | case Break: return "Break"; 76 | case Catch: return "Catch"; 77 | case Const: return "Const"; 78 | case Continue: return "Continue"; 79 | case Do: return "Do"; 80 | case Else: return "Else"; 81 | case Enum: return "Enum"; 82 | case For: return "For"; 83 | case If: return "If"; 84 | case In: return "In"; 85 | case Let: return "Let"; 86 | case Private: return "Private"; 87 | case Public: return "Public"; 88 | case Return: return "Return"; 89 | case Static: return "Static"; 90 | case Struct: return "Struct"; 91 | case Switch: return "Switch"; 92 | case Throw: return "Throw"; 93 | case Try: return "Try"; 94 | case Unchecked: return "Unchecked"; 95 | case Var: return "Var"; 96 | case While: return "While"; 97 | case EndOfFile: return "EndOfFile"; 98 | } 99 | assert_unreachable(); 100 | } 101 | 102 | std::string_view get_fixed_length_lexeme(TokenKind kind) { 103 | using enum TokenKind; 104 | switch (kind) { 105 | case Dot: return "."; 106 | case Comma: return ","; 107 | case Colon: return ":"; 108 | case Semicolon: return ";"; 109 | case LBrace: return "{"; 110 | case RBrace: return "}"; 111 | case LParen: return "("; 112 | case RParen: return ")"; 113 | case LBracket: return "["; 114 | case RBracket: return "]"; 115 | case LAngle: return "<"; 116 | case RAngle: return ">"; 117 | case Eq: return "="; 118 | case Plus: return "+"; 119 | case Minus: return "-"; 120 | case Star: return "*"; 121 | case Slash: return "/"; 122 | case Percent: return "%"; 123 | case Amp: return "&"; 124 | case Pipe: return "|"; 125 | case Tilde: return "~"; 126 | case Caret: return "^"; 127 | case Bang: return "!"; 128 | case Quest: return "?"; 129 | case At: return "@"; 130 | case Dollar: return "$"; 131 | case Hash: return "#"; 132 | case ThinArrow: return "->"; 133 | case ThickArrow: return "=>"; 134 | case ColonColon: return "::"; 135 | case PlusPlus: return "++"; 136 | case MinusMinus: return "--"; 137 | case StarStar: return "**"; 138 | case LAngleLAngle: return "<<"; 139 | case AmpAmp: return "&&"; 140 | case PipePipe: return "||"; 141 | case EqEq: return "=="; 142 | case BangEq: return "!="; 143 | case LAngleEq: return "<="; 144 | case RAngleEq: return ">="; 145 | case PlusEq: return "+="; 146 | case MinusEq: return "-="; 147 | case StarEq: return "*="; 148 | case SlashEq: return "/="; 149 | case PercentEq: return "%="; 150 | case AmpEq: return "&="; 151 | case PipeEq: return "|="; 152 | case TildeEq: return "~="; 153 | case Ellipsis: return "..."; 154 | case StarStarEq: return "**="; 155 | case LAngleLAngleEq: return "<<="; 156 | case RAngleRAngleEq: return ">>="; 157 | case AmpAmpEq: return "&&="; 158 | case PipePipeEq: return "||="; 159 | case Break: return "break"; 160 | case Catch: return "catch"; 161 | case Const: return "const"; 162 | case Continue: return "continue"; 163 | case Do: return "do"; 164 | case Else: return "else"; 165 | case Enum: return "enum"; 166 | case For: return "for"; 167 | case If: return "if"; 168 | case In: return "in"; 169 | case Let: return "let"; 170 | case Private: return "private"; 171 | case Public: return "public"; 172 | case Return: return "return"; 173 | case Static: return "static"; 174 | case Struct: return "struct"; 175 | case Switch: return "switch"; 176 | case Throw: return "throw"; 177 | case Try: return "try"; 178 | case Unchecked: return "unchecked"; 179 | case Var: return "var"; 180 | case While: return "while"; 181 | case EndOfFile: return ""; 182 | default: assert_unreachable(); 183 | } 184 | } 185 | 186 | std::string_view get_token_message_format(TokenKind kind) { 187 | using enum TokenKind; 188 | switch (kind) { 189 | case Name: return "name `{}`"; 190 | 191 | case LineComment: 192 | case BlockComment: return "comment"; 193 | 194 | case DecIntLiteral: 195 | case HexIntLiteral: 196 | case BinIntLiteral: 197 | case OctIntLiteral: return "integer literal `{}`"; 198 | 199 | case FloatLiteral: return "floating-point literal `{}`"; 200 | case CharLiteral: return "character literal {}"; 201 | case StringLiteral: return "string literal {}"; 202 | case EndOfFile: return "end of file"; 203 | 204 | default: return "`{}`"; 205 | } 206 | } 207 | 208 | bool is_variable_length_token(TokenKind kind) { 209 | using enum TokenKind; 210 | switch (kind) { 211 | case Name: 212 | case LineComment: 213 | case BlockComment: 214 | case DecIntLiteral: 215 | case HexIntLiteral: 216 | case BinIntLiteral: 217 | case OctIntLiteral: 218 | case FloatLiteral: 219 | case CharLiteral: 220 | case StringLiteral: return true; 221 | 222 | case Dot: 223 | case Comma: 224 | case Colon: 225 | case Semicolon: 226 | case LBrace: 227 | case RBrace: 228 | case LParen: 229 | case RParen: 230 | case LBracket: 231 | case RBracket: 232 | case LAngle: 233 | case RAngle: 234 | case Eq: 235 | case Plus: 236 | case Minus: 237 | case Star: 238 | case Slash: 239 | case Percent: 240 | case Amp: 241 | case Pipe: 242 | case Tilde: 243 | case Caret: 244 | case Bang: 245 | case Quest: 246 | case At: 247 | case Dollar: 248 | case Hash: 249 | case ThinArrow: 250 | case ThickArrow: 251 | case ColonColon: 252 | case PlusPlus: 253 | case MinusMinus: 254 | case StarStar: 255 | case LAngleLAngle: 256 | case AmpAmp: 257 | case PipePipe: 258 | case EqEq: 259 | case BangEq: 260 | case LAngleEq: 261 | case RAngleEq: 262 | case PlusEq: 263 | case MinusEq: 264 | case StarEq: 265 | case SlashEq: 266 | case PercentEq: 267 | case AmpEq: 268 | case PipeEq: 269 | case TildeEq: 270 | case Ellipsis: 271 | case StarStarEq: 272 | case LAngleLAngleEq: 273 | case RAngleRAngleEq: 274 | case AmpAmpEq: 275 | case PipePipeEq: 276 | case Break: 277 | case Catch: 278 | case Const: 279 | case Continue: 280 | case Do: 281 | case Else: 282 | case Enum: 283 | case For: 284 | case If: 285 | case In: 286 | case Let: 287 | case Private: 288 | case Public: 289 | case Return: 290 | case Static: 291 | case Struct: 292 | case Switch: 293 | case Throw: 294 | case Try: 295 | case Unchecked: 296 | case Var: 297 | case While: 298 | case EndOfFile: return false; 299 | } 300 | assert_unreachable(); 301 | } 302 | 303 | CodeLocation Token::locate_in(const SourceGuard& source, uint8_t tab_size) const { 304 | return source.locate(offset, tab_size); 305 | } 306 | 307 | } // namespace cero 308 | -------------------------------------------------------------------------------- /src/cero/syntax/Token.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Source.hpp" 4 | 5 | namespace cero { 6 | 7 | /// The category of a given token (literals, keywords, punctuation, etc.). 8 | enum class TokenKind { 9 | // Variable-length tokens 10 | Name, 11 | LineComment, 12 | BlockComment, 13 | DecIntLiteral, 14 | HexIntLiteral, 15 | BinIntLiteral, 16 | OctIntLiteral, 17 | FloatLiteral, 18 | CharLiteral, 19 | StringLiteral, 20 | 21 | // One-character tokens 22 | Dot, // . 23 | Comma, // , 24 | Colon, // : 25 | Semicolon, // ; 26 | LBrace, // { 27 | RBrace, // } 28 | LParen, // ( 29 | RParen, // ) 30 | LBracket, // [ 31 | RBracket, // ] 32 | LAngle, // < 33 | RAngle, // > 34 | Eq, // = 35 | Plus, // + 36 | Minus, // - 37 | Star, // * 38 | Slash, // / 39 | Percent, // % 40 | Amp, // & 41 | Pipe, // | 42 | Tilde, // ~ 43 | Caret, // ^ 44 | Bang, // ! 45 | Quest, // ? 46 | At, // @ 47 | Dollar, // $ 48 | Hash, // # 49 | 50 | // Two-character tokens 51 | ThinArrow, // -> 52 | ThickArrow, // => 53 | ColonColon, // :: 54 | PlusPlus, // ++ 55 | MinusMinus, // -- 56 | StarStar, // ** 57 | LAngleLAngle, // << 58 | AmpAmp, // && 59 | PipePipe, // || 60 | EqEq, // == 61 | BangEq, // != 62 | LAngleEq, // <= 63 | RAngleEq, // >= 64 | PlusEq, // += 65 | MinusEq, // -= 66 | StarEq, // *= 67 | SlashEq, // /= 68 | PercentEq, // %= 69 | AmpEq, // &= 70 | PipeEq, // |= 71 | TildeEq, // ~= 72 | 73 | // Three-character tokens 74 | Ellipsis, // ... 75 | StarStarEq, // **= 76 | LAngleLAngleEq, // <<= 77 | RAngleRAngleEq, // >>= 78 | AmpAmpEq, // &&= 79 | PipePipeEq, // ||= 80 | 81 | // Keywords 82 | Break, 83 | Catch, 84 | Const, 85 | Continue, 86 | Do, 87 | Else, 88 | Enum, 89 | For, 90 | If, 91 | In, 92 | Let, 93 | Private, 94 | Public, 95 | Return, 96 | Static, 97 | Struct, 98 | Switch, 99 | Throw, 100 | Try, 101 | Unchecked, 102 | Var, 103 | While, 104 | 105 | EndOfFile, 106 | }; 107 | 108 | std::string_view token_kind_to_string(TokenKind kind); 109 | std::string_view get_fixed_length_lexeme(TokenKind kind); 110 | std::string_view get_token_message_format(TokenKind kind); 111 | bool is_variable_length_token(TokenKind kind); 112 | 113 | struct Token { 114 | TokenKind kind : 8 = {}; 115 | SourceOffset offset : SourceOffsetBits = 0; 116 | 117 | CodeLocation locate_in(const SourceGuard& source, uint8_t tab_size) const; 118 | }; 119 | 120 | static_assert(sizeof(Token) == 4); 121 | 122 | } // namespace cero 123 | -------------------------------------------------------------------------------- /src/cero/syntax/TokenCursor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Encoding.hpp" 4 | #include "cero/syntax/TokenStream.hpp" 5 | 6 | namespace cero { 7 | 8 | /// Iterates over a token stream. An instance of this class must not outlive the token stream it is initialized with. 9 | class TokenCursor { 10 | public: 11 | /// Creates a cursor positioned at the first token of the given token stream. 12 | explicit TokenCursor(const TokenStream& token_stream) : 13 | it_(token_stream.array().begin()) { 14 | } 15 | 16 | /// Returns the current token. 17 | Token peek() { 18 | return *it_; 19 | } 20 | 21 | /// Returns the current token kind. 22 | TokenKind peek_kind() { 23 | return it_->kind; 24 | } 25 | 26 | /// Returns the current token offset. 27 | SourceOffset peek_offset() { 28 | return it_->offset; 29 | } 30 | 31 | /// Returns the current token and then advances if not at the end. 32 | Token next() { 33 | const auto token = *it_; 34 | advance(); 35 | return token; 36 | } 37 | 38 | /// Returns true and advances if the current token kind equals the expected, otherwise returns false. 39 | bool match(TokenKind kind) { 40 | if (it_->kind == kind) { 41 | advance(); 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | /// Returns the current token and advances if the current token kind equals the expected, otherwise returns null. 48 | std::optional match_token(TokenKind kind) { 49 | auto token = *it_; 50 | if (token.kind == kind) { 51 | advance(); 52 | return token; 53 | } 54 | return std::nullopt; 55 | } 56 | 57 | /// Returns a string view of the lexeme and advances if the current token kind is an identifier token, otherwise returns an 58 | /// empty string. 59 | std::string_view match_name(const SourceGuard& source) { 60 | if (it_->kind == TokenKind::Name) { 61 | auto identifier = get_lexeme(source); 62 | advance(); 63 | return identifier; 64 | } 65 | return {}; 66 | } 67 | 68 | /// Returns the token after the current token. 69 | Token peek_ahead() { 70 | auto saved = it_; 71 | advance(); 72 | auto token = *it_; 73 | it_ = saved; 74 | return token; 75 | } 76 | 77 | std::string_view get_lexeme(const SourceGuard& source) const { 78 | const auto offset = it_->offset; 79 | const auto length = it_->kind != TokenKind::EndOfFile ? it_[1].offset - offset : 0; 80 | 81 | auto lexeme = source.get_text().substr(offset, length); 82 | 83 | size_t whitespace = 0; 84 | const auto end = lexeme.rend(); 85 | for (auto it = lexeme.rbegin(); it != end; ++it) { 86 | if (is_whitespace(*it)) { 87 | ++whitespace; 88 | } else { 89 | break; 90 | } 91 | } 92 | lexeme.remove_suffix(whitespace); 93 | 94 | return lexeme; 95 | } 96 | 97 | /// Moves cursor to the next token. 98 | void advance() { 99 | if (it_->kind != TokenKind::EndOfFile) { 100 | ++it_; 101 | } 102 | } 103 | 104 | /// Advance to the next non-comment token. 105 | void skip_comments() { 106 | auto kind = it_->kind; 107 | while (kind == TokenKind::LineComment || kind == TokenKind::BlockComment) { 108 | ++it_; 109 | kind = it_->kind; 110 | } 111 | } 112 | 113 | private: 114 | std::span::iterator it_; 115 | }; 116 | 117 | } // namespace cero 118 | -------------------------------------------------------------------------------- /src/cero/syntax/TokenStream.cpp: -------------------------------------------------------------------------------- 1 | #include "TokenStream.hpp" 2 | 3 | #include "cero/syntax/TokenCursor.hpp" 4 | 5 | namespace cero { 6 | 7 | uint32_t TokenStream::num_tokens() const { 8 | return static_cast(stream_.size()); 9 | } 10 | 11 | bool TokenStream::has_errors() const { 12 | return has_errors_; 13 | } 14 | 15 | std::span TokenStream::array() const { 16 | return {stream_}; 17 | } 18 | 19 | std::string TokenStream::to_string(const SourceGuard& source, uint8_t tab_size) const { 20 | auto num_tokens = stream_.size(); 21 | auto str = fmt::format("Token stream for {} ({} token{})\n", source.get_name(), num_tokens, num_tokens == 1 ? "" : "s"); 22 | 23 | TokenCursor cursor(*this); 24 | while (true) { 25 | auto lexeme = cursor.get_lexeme(source); 26 | auto token = cursor.next(); 27 | 28 | auto kind_str = token_kind_to_string(token.kind); 29 | 30 | // This loop is technically quadratic since SourceGuard::locate will linearly search the source for the code location 31 | // every time, but it really doesn't matter. 32 | auto location_str = token.locate_in(source, tab_size).to_short_string(); 33 | 34 | str += fmt::format("\t{} `{}` {}\n", kind_str, lexeme, location_str); 35 | if (token.kind == TokenKind::EndOfFile) { 36 | break; 37 | } 38 | } 39 | 40 | return str; 41 | } 42 | 43 | TokenStream::TokenStream(const SourceGuard& source) { 44 | // TODO: find the most common ratio between source length and token count and then reserve based on that 45 | stream_.reserve(source.get_length()); 46 | } 47 | 48 | void TokenStream::add_token(TokenKind kind, SourceOffset offset) { 49 | stream_.emplace_back(Token {kind, offset & 0x00ffffffu}); 50 | } 51 | 52 | } // namespace cero 53 | -------------------------------------------------------------------------------- /src/cero/syntax/TokenStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cero/syntax/Token.hpp" 4 | 5 | namespace cero { 6 | 7 | class TokenStream { 8 | public: 9 | /// Number of tokens in the stream. 10 | uint32_t num_tokens() const; 11 | 12 | /// Whether syntax errors were encountered during lexing. 13 | bool has_errors() const; 14 | 15 | /// Get a view of the underlying array of tokens. 16 | std::span array() const; 17 | 18 | /// Creates a list-like string representation of the token stream. 19 | std::string to_string(const SourceGuard& source, uint8_t tab_size) const; 20 | 21 | private: 22 | std::vector stream_; 23 | bool has_errors_ = false; 24 | 25 | /// Reserves storage for the token stream based on the length of the source code input. 26 | explicit TokenStream(const SourceGuard& source); 27 | 28 | /// Appends a token to the stream. 29 | void add_token(TokenKind kind, SourceOffset offset); 30 | 31 | friend class Lexer; 32 | }; 33 | 34 | } // namespace cero 35 | -------------------------------------------------------------------------------- /src/cero/util/Assert.cpp: -------------------------------------------------------------------------------- 1 | #include "Assert.hpp" 2 | 3 | namespace cero { 4 | 5 | [[noreturn]] static void fail(std::source_location location) { 6 | fmt::println("\tFile: {}", location.file_name()); 7 | fmt::println("\tFunction: {}", location.function_name()); 8 | std::abort(); 9 | } 10 | 11 | void assert_fail(std::string_view msg, std::source_location location) { 12 | fmt::println("Assertion failed: {}", msg); 13 | fail(location); 14 | } 15 | 16 | void assert_unreachable(std::source_location location) { 17 | cero_debug_break(); 18 | fmt::println("The compiler reached code that should be unreachable."); 19 | fail(location); 20 | } 21 | 22 | void to_do(std::source_location location) { 23 | cero_debug_break(); 24 | fmt::println("Not yet implemented."); 25 | fail(location); 26 | } 27 | 28 | } // namespace cero 29 | -------------------------------------------------------------------------------- /src/cero/util/Assert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// Terminates the compiler immediately and unconditionally. To be used when an expected precondition, result or invariant 6 | /// was not upheld. 7 | [[noreturn]] void assert_fail(std::string_view msg, std::source_location location = std::source_location::current()); 8 | 9 | /// Terminates the compiler immediately and unconditionally. To be used in control flow branches that are only reachable if 10 | /// there are logic bugs in the compiler. 11 | [[noreturn]] void assert_unreachable(std::source_location location = std::source_location::current()); 12 | 13 | /// Terminates the compiler immediately and unconditionally. To be used where something is not yet implemented. 14 | [[noreturn]] void to_do(std::source_location location = std::source_location::current()); 15 | 16 | } // namespace cero 17 | 18 | #if CERO_COMPILER_CLANG 19 | #define cero_debug_break() __builtin_debugtrap() 20 | #elif CERO_COMPILER_MSVC 21 | #define cero_debug_break() __debugbreak() 22 | #elif CERO_COMPILER_GCC 23 | #include 24 | #define cero_debug_break() std::raise(SIGTRAP) 25 | #else 26 | #error unknown compiler 27 | #endif 28 | 29 | /// Terminates the compiler immediately if the condition is false. To be used when an expected precondition, result or invariant 30 | /// was not upheld. 31 | #define cero_assert(condition, msg, ...) \ 32 | do { \ 33 | if (!(condition)) { \ 34 | cero_debug_break(); \ 35 | cero::assert_fail(fmt::format(msg, __VA_ARGS__)); \ 36 | } \ 37 | } while (false) 38 | 39 | #ifndef NDEBUG 40 | #define cero_assert_debug(condition, msg, ...) cero_assert(condition, msg, __VA_ARGS__) 41 | #else 42 | #define cero_assert_debug(condition, msg, ...) static_cast(condition) 43 | #endif 44 | -------------------------------------------------------------------------------- /src/cero/util/FileMapping.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UniqueImpl.hpp" 4 | 5 | namespace cero { 6 | 7 | class FileMapping { 8 | public: 9 | static std::expected from(std::string_view path); 10 | 11 | std::string_view get_text() const; 12 | size_t get_size() const; 13 | 14 | ~FileMapping(); 15 | FileMapping(FileMapping&&) noexcept; 16 | FileMapping& operator=(FileMapping&&) noexcept; 17 | 18 | private: 19 | UniqueImpl _impl; 20 | 21 | FileMapping(); 22 | }; 23 | 24 | } // namespace cero 25 | -------------------------------------------------------------------------------- /src/cero/util/FileMapping.posix.cpp: -------------------------------------------------------------------------------- 1 | #include "FileMapping.hpp" 2 | 3 | #include "Assert.hpp" 4 | #include "SystemError.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cero { 12 | 13 | struct FileMappingImpl { 14 | int fd = -1; 15 | size_t size = 0; 16 | void* addr = MAP_FAILED; 17 | 18 | void destroy() const { 19 | if (addr != MAP_FAILED) { 20 | int ret = ::munmap(addr, size); 21 | cero_assert(ret != -1, "could not unmap file, {}", get_last_system_error_message()); 22 | } 23 | if (fd != -1) { 24 | int ret = ::close(fd); 25 | cero_assert(ret != -1, "could not close file, {}", get_last_system_error_message()); 26 | } 27 | } 28 | }; 29 | 30 | std::unexpected FileMapping::from(std::string_view path) { 31 | std::string path_nt(path); 32 | 33 | FileMapping f; 34 | f._impl->fd = ::open(path_nt.c_str(), O_RDONLY); 35 | if (f._impl->fd == -1) { 36 | return std::unexpected(get_last_system_error()); 37 | } 38 | 39 | struct stat file_stats = {}; 40 | int ret = ::fstat(f._impl->fd, &file_stats); 41 | if (ret == -1) { 42 | return std::unexpected(get_last_system_error()); 43 | } 44 | f._impl->size = static_cast(file_stats.st_size); 45 | 46 | if (f._impl->size > 0) { 47 | f._impl->addr = ::mmap(nullptr, f._impl->size, PROT_READ, MAP_PRIVATE, f._impl->fd, 0); 48 | if (f._impl->addr == MAP_FAILED) { 49 | return std::unexpected(get_last_system_error()); 50 | } 51 | } 52 | 53 | return f; 54 | } 55 | 56 | std::string_view FileMapping::get_text() const { 57 | if (_impl->size == 0) { 58 | return ""; 59 | } else { 60 | return std::string_view(static_cast(_impl->addr), _impl->size); 61 | } 62 | } 63 | 64 | size_t FileMapping::get_size() const { 65 | return _impl->size; 66 | } 67 | 68 | FileMapping::FileMapping() = default; 69 | FileMapping::~FileMapping() = default; 70 | FileMapping::FileMapping(FileMapping&&) noexcept = default; 71 | FileMapping& FileMapping::operator=(FileMapping&&) noexcept = default; 72 | 73 | } // namespace cero 74 | -------------------------------------------------------------------------------- /src/cero/util/FileMapping.win.cpp: -------------------------------------------------------------------------------- 1 | #include "FileMapping.hpp" 2 | 3 | #include "Assert.hpp" 4 | #include "SystemError.hpp" 5 | #include "WindowsApi.hpp" 6 | #include "WindowsUtil.hpp" 7 | 8 | namespace cero { 9 | 10 | struct FileMappingImpl { 11 | HANDLE file = INVALID_HANDLE_VALUE; 12 | size_t size = 0; 13 | HANDLE mapping = INVALID_HANDLE_VALUE; 14 | LPVOID addr = nullptr; 15 | 16 | void destroy() const { 17 | if (addr != nullptr) { 18 | BOOL success = ::UnmapViewOfFile(addr); 19 | cero_assert(success, "could not unmap file, {}", get_last_system_error_message()); 20 | } 21 | if (mapping != INVALID_HANDLE_VALUE) { 22 | BOOL success = ::CloseHandle(mapping); 23 | cero_assert(success, "could not close file mapping handle, {}", get_last_system_error_message()); 24 | } 25 | if (file != INVALID_HANDLE_VALUE) { 26 | BOOL success = ::CloseHandle(file); 27 | cero_assert(success, "could not close file handle, {}", get_last_system_error_message()); 28 | } 29 | } 30 | }; 31 | 32 | std::expected FileMapping::from(std::string_view path) { 33 | auto path_utf16 = windows::utf8_to_utf16(path); 34 | 35 | FileMapping f; 36 | f._impl->file = ::CreateFileW(path_utf16.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 37 | FILE_ATTRIBUTE_NORMAL, nullptr); 38 | if (f._impl->file == INVALID_HANDLE_VALUE) { 39 | return std::unexpected(get_last_system_error()); 40 | } 41 | 42 | LARGE_INTEGER file_size; 43 | BOOL success = ::GetFileSizeEx(f._impl->file, &file_size); 44 | if (!success) { 45 | return std::unexpected(get_last_system_error()); 46 | } 47 | f._impl->size = static_cast(file_size.QuadPart); 48 | 49 | if (f._impl->size > 0) { 50 | HANDLE mapping = ::CreateFileMappingW(f._impl->file, nullptr, PAGE_READONLY, 0, 0, nullptr); 51 | if (mapping == nullptr) { 52 | return std::unexpected(get_last_system_error()); 53 | } 54 | f._impl->mapping = mapping; 55 | 56 | f._impl->addr = ::MapViewOfFile(f._impl->mapping, FILE_MAP_READ, 0, 0, 0); 57 | if (f._impl->addr == nullptr) { 58 | return std::unexpected(get_last_system_error()); 59 | } 60 | } 61 | 62 | return f; 63 | } 64 | 65 | std::string_view FileMapping::get_text() const { 66 | if (_impl->size == 0) { 67 | return ""; 68 | } else { 69 | return std::string_view(static_cast(_impl->addr), _impl->size); 70 | } 71 | } 72 | 73 | size_t FileMapping::get_size() const { 74 | return _impl->size; 75 | } 76 | 77 | FileMapping::FileMapping() = default; 78 | FileMapping::~FileMapping() = default; 79 | FileMapping::FileMapping(FileMapping&&) noexcept = default; 80 | FileMapping& FileMapping::operator=(FileMapping&&) noexcept = default; 81 | 82 | } // namespace cero 83 | -------------------------------------------------------------------------------- /src/cero/util/ImplStorage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /** 6 | * Helper for hiding implementation details without dynamic allocation. 7 | */ 8 | template 9 | class ImplStorage { 10 | public: 11 | ImplStorage() { // NOLINT(*-pro-type-member-init) 12 | static_assert_size_align(); 13 | new (storage_) T(); 14 | } 15 | 16 | explicit(false) ImplStorage(T&& t) { // NOLINT(*-pro-type-member-init) 17 | static_assert_size_align(); 18 | new (storage_) T(std::move(t)); 19 | } 20 | 21 | ImplStorage(ImplStorage&& other) noexcept { // NOLINT(*-pro-type-member-init) 22 | static_assert_size_align(); 23 | new (storage_) T(std::move(*other)); 24 | } 25 | 26 | ~ImplStorage() { 27 | (*this)->~T(); 28 | } 29 | 30 | ImplStorage& operator=(ImplStorage&& other) noexcept { 31 | **this = std::move(*other); 32 | return *this; 33 | } 34 | 35 | T& operator*() & noexcept { 36 | return *std::launder(reinterpret_cast(storage_)); 37 | } 38 | 39 | const T& operator*() const& noexcept { 40 | return *std::launder(reinterpret_cast(storage_)); 41 | } 42 | 43 | T&& operator*() && noexcept { 44 | return std::move(*std::launder(reinterpret_cast(storage_))); 45 | } 46 | 47 | T* operator->() noexcept { 48 | return std::launder(reinterpret_cast(storage_)); 49 | } 50 | 51 | const T* operator->() const noexcept { 52 | return std::launder(reinterpret_cast(storage_)); 53 | } 54 | 55 | private: 56 | alignas(Align) unsigned char storage_[Size]; 57 | 58 | static void static_assert_size_align() { 59 | static_assert(Size >= sizeof(T) && Align >= alignof(T), "Specified size and alignment must be greater or equal to the " 60 | "size and alignment of T."); 61 | } 62 | }; 63 | 64 | } // namespace cero 65 | -------------------------------------------------------------------------------- /src/cero/util/ScopedAssign.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// Takes an object and assigns it a new value after remembering its old value. The object's old value is restored at the end of 6 | /// the scope. 7 | template 8 | class ScopedAssign { 9 | public: 10 | template 11 | ScopedAssign(T& object, U&& new_value) : 12 | object_(object), 13 | old_value_(std::exchange(object, std::forward(new_value))) { 14 | } 15 | 16 | ~ScopedAssign() { 17 | object_ = std::move(old_value_); 18 | } 19 | 20 | ScopedAssign(ScopedAssign&&) = delete; 21 | 22 | private: 23 | T& object_; 24 | T old_value_; 25 | }; 26 | 27 | } // namespace cero 28 | -------------------------------------------------------------------------------- /src/cero/util/SystemError.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | /// Gets the most recent system API error. 6 | std::error_condition get_last_system_error(); 7 | 8 | /// Gets the message associated with the most recent system API error. 9 | inline std::string get_last_system_error_message() { 10 | auto error = get_last_system_error(); 11 | return fmt::format("system error 0x{:x} (\"{}\")", error.value(), error.message()); 12 | } 13 | 14 | } // namespace cero 15 | -------------------------------------------------------------------------------- /src/cero/util/SystemError.posix.cpp: -------------------------------------------------------------------------------- 1 | #include "SystemError.hpp" 2 | 3 | std::error_condition cero::get_last_system_error() { 4 | const int last_error = errno; 5 | return std::system_category().default_error_condition(last_error); 6 | } 7 | -------------------------------------------------------------------------------- /src/cero/util/SystemError.win.cpp: -------------------------------------------------------------------------------- 1 | #include "SystemError.hpp" 2 | 3 | #include "cero/util/WindowsApi.hpp" 4 | 5 | std::error_condition cero::get_last_system_error() { 6 | const DWORD last_error = ::GetLastError(); 7 | return std::system_category().default_error_condition(last_error); 8 | } 9 | -------------------------------------------------------------------------------- /src/cero/util/Traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cero { 6 | 7 | template 8 | constexpr inline bool always_false = false; 9 | 10 | template 11 | constexpr inline bool are_trivially_destructible = (... && std::is_trivially_destructible_v); 12 | 13 | template 14 | constexpr inline bool are_trivially_copy_constructible = (... && std::is_trivially_copy_constructible_v); 15 | 16 | template 17 | constexpr inline bool are_trivially_copy_assignable = (... && std::is_trivially_copy_assignable_v); 18 | 19 | template 20 | constexpr inline bool are_trivially_move_constructible = (... && std::is_trivially_move_constructible_v); 21 | 22 | template 23 | constexpr inline bool are_trivially_move_assignable = (... && std::is_trivially_move_assignable_v); 24 | 25 | } // namespace cero 26 | -------------------------------------------------------------------------------- /src/cero/util/UniqueImpl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero { 4 | 5 | template 6 | class UniqueImpl { 7 | public: 8 | UniqueImpl() { // NOLINT(*-pro-type-member-init) 9 | static_assert_size_align(); 10 | new (storage_) T(); 11 | } 12 | 13 | explicit(false) UniqueImpl(T&& t) { // NOLINT(*-pro-type-member-init) 14 | static_assert_size_align(); 15 | new (storage_) T(std::move(t)); 16 | } 17 | 18 | UniqueImpl(UniqueImpl&& other) noexcept { // NOLINT(*-pro-type-member-init) 19 | static_assert_size_align(); 20 | new (storage_) T(std::exchange(*other, T())); 21 | } 22 | 23 | ~UniqueImpl() { 24 | (*this)->destroy(); 25 | } 26 | 27 | UniqueImpl& operator=(UniqueImpl&& other) noexcept { 28 | if (this != &other) { 29 | (*this)->destroy(); 30 | **this = std::exchange(*other, T()); 31 | } 32 | return *this; 33 | } 34 | 35 | T& operator*() & noexcept { 36 | return *std::launder(reinterpret_cast(storage_)); 37 | } 38 | 39 | const T& operator*() const& noexcept { 40 | return *std::launder(reinterpret_cast(storage_)); 41 | } 42 | 43 | T&& operator*() && noexcept { 44 | return std::move(*std::launder(reinterpret_cast(storage_))); 45 | } 46 | 47 | T* operator->() noexcept { 48 | return std::launder(reinterpret_cast(storage_)); 49 | } 50 | 51 | const T* operator->() const noexcept { 52 | return std::launder(reinterpret_cast(storage_)); 53 | } 54 | 55 | private: 56 | alignas(Align) unsigned char storage_[Size]; 57 | 58 | static void static_assert_size_align() { 59 | static_assert(Size >= sizeof(T) && Align >= alignof(T), "Specified size and alignment must be greater or equal to the " 60 | "size and alignment of T."); 61 | } 62 | }; 63 | 64 | } // namespace cero 65 | -------------------------------------------------------------------------------- /src/cero/util/WindowsApi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | 5 | #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ 6 | #define NOVIRTUALKEYCODES // VK_* 7 | #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* 8 | #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* 9 | #define NOSYSMETRICS // SM_* 10 | #define NOMENUS // MF_* 11 | #define NOICONS // IDI_* 12 | #define NOKEYSTATES // MK_* 13 | #define NOSYSCOMMANDS // SC_* 14 | #define NORASTEROPS // Binary and Tertiary raster ops 15 | #define NOSHOWWINDOW // SW_* 16 | #define OEMRESOURCE // OEM Resource values 17 | #define NOATOM // Atom Manager routines 18 | #define NOCLIPBOARD // Clipboard routines 19 | #define NOCOLOR // Screen colors 20 | #define NOCTLMGR // Control and Dialog routines 21 | #define NODRAWTEXT // DrawText() and DT_* 22 | #define NOGDI // All GDI defines and routines 23 | #define NOKERNEL // All KERNEL defines and routines 24 | #define NOUSER // All USER defines and routines 25 | #define NOMB // MB_* and MessageBox() 26 | #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines 27 | #define NOMETAFILE // typedef METAFILEPICT 28 | #define NOMINMAX // Macros min(a,b) and max(a,b) 29 | #define NOMSG // typedef MSG and associated routines 30 | #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* 31 | #define NOSCROLL // SB_* and scrolling routines 32 | #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. 33 | #define NOSOUND // Sound driver routines 34 | #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines 35 | #define NOWH // SetWindowsHook and WH_* 36 | #define NOWINOFFSETS // GWL_*, GCL_*, associated routines 37 | #define NOCOMM // COMM driver routines 38 | #define NOKANJI // Kanji support stuff. 39 | #define NOHELP // Help engine interface. 40 | #define NOPROFILER // Profiler interface. 41 | #define NODEFERWINDOWPOS // DeferWindowPos routines 42 | #define NOMCX // Modem Configuration Extensions 43 | 44 | #include 45 | -------------------------------------------------------------------------------- /src/cero/util/WindowsUtil.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cero::windows { 4 | 5 | std::wstring utf8_to_utf16(std::string_view input); 6 | std::string utf16_to_utf8(std::wstring_view input); 7 | 8 | } // namespace cero::windows 9 | -------------------------------------------------------------------------------- /src/cero/util/WindowsUtil.win.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowsUtil.hpp" 2 | 3 | #include "cero/util/WindowsApi.hpp" 4 | 5 | namespace cero::windows { 6 | 7 | std::wstring utf8_to_utf16(std::string_view input) { 8 | const size_t in_len = input.length(); 9 | std::wstring output(in_len, L'\0'); 10 | 11 | // MultiByteToWideChar does not accept length zero 12 | if (in_len != 0) { 13 | const int len = static_cast(in_len); 14 | const int out_len = ::MultiByteToWideChar(CP_UTF8, 0, input.data(), len, output.data(), len); 15 | 16 | // cut down to actual number of code units 17 | output.resize(static_cast(out_len)); 18 | } 19 | return output; 20 | } 21 | 22 | std::string utf16_to_utf8(std::wstring_view input) { 23 | const size_t in_len = input.length(); 24 | 25 | // maximum number of UTF-8 code units that a UTF-16 sequence could convert to 26 | const size_t cap = 3 * in_len; 27 | std::string output(cap, '\0'); 28 | 29 | // WideCharToMultiByte does not accept length zero 30 | if (in_len != 0) { 31 | const int out_len = ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(in_len), output.data(), 32 | static_cast(cap), nullptr, nullptr); 33 | 34 | // cut down to actual number of code units 35 | output.resize(static_cast(out_len)); 36 | } 37 | return output; 38 | } 39 | 40 | } // namespace cero::windows 41 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE sources CONFIGURE_DEPENDS *) 2 | list(FILTER sources INCLUDE REGEX ${source_filter_regex}) 3 | 4 | add_executable(cero_tests ${sources}) 5 | 6 | target_precompile_headers(cero_tests REUSE_FROM cero) 7 | target_include_directories(cero_tests PRIVATE .) 8 | 9 | find_package(doctest REQUIRED) 10 | 11 | target_link_libraries(cero_tests PRIVATE 12 | cero 13 | doctest::doctest 14 | ) 15 | -------------------------------------------------------------------------------- /tests/common/ExhaustiveReporter.cpp: -------------------------------------------------------------------------------- 1 | #include "ExhaustiveReporter.hpp" 2 | 3 | #include "common/Test.hpp" 4 | 5 | #include 6 | 7 | namespace tests { 8 | 9 | ExhaustiveReporter::ExhaustiveReporter() : 10 | source_name_(get_current_test_name()) { 11 | } 12 | 13 | ExhaustiveReporter::~ExhaustiveReporter() { 14 | // If this fails, not all expected reports were seen. 15 | CHECK(expected_reports_.empty()); 16 | } 17 | 18 | bool ExhaustiveReporter::has_errors() const { 19 | return has_errors_; 20 | } 21 | 22 | void ExhaustiveReporter::expect(uint32_t line, uint32_t column, cero::Message message, cero::MessageArgs args) { 23 | CHECK(args.valid_for_message(message)); 24 | auto format = cero::get_message_format_string(message); 25 | auto message_text = fmt::vformat(format, args.store); 26 | 27 | cero::CodeLocation location {source_name_, line, column}; 28 | expected_reports_.emplace(location, std::move(message_text)); 29 | } 30 | 31 | void ExhaustiveReporter::set_source_name(std::string_view source_name) { 32 | source_name_ = source_name; 33 | } 34 | 35 | void ExhaustiveReporter::handle_report(cero::CodeLocation location, cero::MessageLevel, std::string message_text) { 36 | const bool reports_are_done = expected_reports_.empty(); 37 | REQUIRE(!reports_are_done); // If this fails, every expected report was already seen and an unexpected one was received. 38 | 39 | const Report& expected = expected_reports_.front(); 40 | const Report received {location, std::move(message_text)}; 41 | const bool report_matches = expected == received; 42 | CHECK(report_matches); // If this fails, the received report does not match the report that is expected to be seen next. 43 | 44 | if (report_matches) { 45 | expected_reports_.pop(); 46 | } 47 | } 48 | 49 | } // namespace tests 50 | -------------------------------------------------------------------------------- /tests/common/ExhaustiveReporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace tests { 8 | 9 | /// A test utility that triggers a test failure if there are unexpected messages left after running the compiler. 10 | class ExhaustiveReporter : public cero::Reporter { 11 | public: 12 | /// Creates a new exhaustive reporter where the current test name is the source name for reports. 13 | ExhaustiveReporter(); 14 | 15 | ExhaustiveReporter(ExhaustiveReporter&&) = delete; 16 | 17 | /// Checks whether all expected reports were seen. 18 | ~ExhaustiveReporter() override; 19 | 20 | bool has_errors() const override; 21 | 22 | /// Marks a report as expected with the currently set source name, given line, column, message kind and arguments. Instead 23 | /// of expecting specific strings, this way allows decoupling the tests from the exact text of a diagnostic message. 24 | void expect(uint32_t line, uint32_t column, cero::Message message, cero::MessageArgs args); 25 | 26 | /// Sets the current source name used for expected reports. 27 | void set_source_name(std::string_view source_name); 28 | 29 | private: 30 | struct Report { 31 | cero::CodeLocation location; 32 | std::string message_text; 33 | 34 | bool operator==(const Report&) const = default; 35 | }; 36 | 37 | std::queue expected_reports_; 38 | std::string_view source_name_; 39 | bool has_errors_ = false; 40 | 41 | void handle_report(cero::CodeLocation location, cero::MessageLevel message_level, std::string message_text) override; 42 | }; 43 | 44 | } // namespace tests 45 | -------------------------------------------------------------------------------- /tests/common/Test.cpp: -------------------------------------------------------------------------------- 1 | #include "Test.hpp" 2 | 3 | #include 4 | 5 | namespace tests { 6 | 7 | cero::SourceGuard make_test_source(std::string_view source_text) { 8 | std::string_view name = get_current_test_name(); 9 | return cero::Source::from_string(name, source_text).lock().value(); 10 | } 11 | 12 | void build_test_source(cero::Reporter& reporter, std::string_view source_text) { 13 | std::string_view name = get_current_test_name(); 14 | auto source = cero::Source::from_string(name, source_text); 15 | 16 | cero::CompilerOptions options; 17 | cero::try_build_source(source, reporter, options); 18 | } 19 | 20 | } // namespace tests 21 | -------------------------------------------------------------------------------- /tests/common/Test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace tests { 9 | 10 | cero::SourceGuard make_test_source(std::string_view source_text); 11 | 12 | void build_test_source(cero::Reporter& reporter, std::string_view source_text); 13 | 14 | std::string_view get_current_test_name(); 15 | 16 | /// Creates and registers a test case. 17 | #define CERO_TEST(NAME) DOCTEST_CREATE_AND_REGISTER_FUNCTION(NAME, #NAME) 18 | 19 | constexpr inline uint8_t TabSize = 4; 20 | 21 | } // namespace tests 22 | -------------------------------------------------------------------------------- /tests/common/TestRunner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 4 | #include 5 | #include 6 | 7 | namespace tests { 8 | 9 | std::string_view get_current_test_name() { 10 | // Workaround because Doctest doesn't have an API for this yet. Also, it must be implemented in the file that defines 11 | // DOCTEST_CONFIG_IMPLEMENT as described in https://github.com/doctest/doctest/issues/345. 12 | return doctest::detail::g_cs->currentTest->m_name; 13 | } 14 | 15 | /// Implements custom console output for the testing framework. 16 | class TestRunner : public doctest::IReporter { 17 | public: 18 | explicit TestRunner(const doctest::ContextOptions& in) : 19 | out(*in.cout) { 20 | cero::ConsoleReporter::init_console_environment(); 21 | } 22 | 23 | void report_query(const doctest::QueryData&) override { 24 | } 25 | 26 | void test_run_start() override { 27 | } 28 | 29 | void test_run_end(const doctest::TestRunStats&) override { 30 | } 31 | 32 | void test_case_start(const doctest::TestCaseData& test_case) override { 33 | fmt::print(out, "Test: {}", test_case.m_name); 34 | } 35 | 36 | void test_case_reenter(const doctest::TestCaseData&) override { 37 | } 38 | 39 | void test_case_end(const doctest::CurrentTestCaseStats& stats) override { 40 | if (stats.testCaseSuccess) { 41 | fmt::println(out, "--- PASSED"); 42 | } 43 | } 44 | 45 | void test_case_exception(const doctest::TestCaseException&) override { 46 | } 47 | 48 | void subcase_start(const doctest::SubcaseSignature&) override { 49 | } 50 | 51 | void subcase_end() override { 52 | } 53 | 54 | void log_assert(const doctest::AssertData&) override { 55 | } 56 | 57 | void log_message(const doctest::MessageData&) override { 58 | } 59 | 60 | void test_case_skipped(const doctest::TestCaseData&) override { 61 | } 62 | 63 | private: 64 | std::ostream& out; 65 | }; 66 | 67 | } // namespace tests 68 | 69 | DOCTEST_REGISTER_REPORTER("tests::TestRunner", 1, tests::TestRunner); 70 | -------------------------------------------------------------------------------- /tests/driver/BuildCommandTests.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | #include 5 | 6 | namespace tests { 7 | 8 | CERO_TEST(FileNotFoundForBuildCommand) { 9 | ExhaustiveReporter r; 10 | r.set_source_name("FileShouldNotExist.ce"); 11 | r.expect(0, 0, cero::Message::FileNotFound, cero::MessageArgs()); 12 | 13 | auto source = cero::Source::from_file("FileShouldNotExist.ce"); 14 | cero::try_build_source(source, r, {}); 15 | } 16 | 17 | CERO_TEST(CouldNotOpenFileForBuildCommand) { 18 | #if CERO_SYSTEM_WINDOWS 19 | constexpr auto err_code = std::errc::permission_denied; 20 | #else 21 | constexpr auto err_code = std::errc::no_such_device; 22 | #endif 23 | const auto err_msg = std::make_error_condition(err_code).message(); 24 | 25 | ExhaustiveReporter r; 26 | r.set_source_name("."); 27 | r.expect(0, 0, cero::Message::CouldNotOpenFile, cero::MessageArgs(err_msg)); 28 | 29 | auto source = cero::Source::from_file("."); 30 | cero::try_build_source(source, r, {}); 31 | } 32 | 33 | } // namespace tests 34 | -------------------------------------------------------------------------------- /tests/syntax/AstCompare.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCompare.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace tests { 7 | 8 | AstCompare::AstCompare(const cero::Ast& ast) : 9 | cursor_(ast) { 10 | expected_kind_.push(cero::AstNodeKind::Root); 11 | 12 | cursor_.visit_one(*this); 13 | root_count_ = expected_child_count_.pop(); 14 | } 15 | 16 | AstCompare::~AstCompare() { 17 | CHECK_EQ(current_child_count_, root_count_); 18 | } 19 | 20 | void AstCompare::visit(const cero::AstRoot& root) { 21 | expect(root); 22 | } 23 | 24 | void AstCompare::struct_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs) { 25 | record(cero::AstNodeKind::StructDefinition); 26 | expected_access_.push(access); 27 | expected_name_.push(name); 28 | 29 | visit_children(cs); 30 | } 31 | 32 | void AstCompare::visit(const cero::AstStructDefinition& struct_def) { 33 | expect(struct_def); 34 | CHECK_EQ(expected_access_.pop(), struct_def.access); 35 | CHECK_EQ(expected_name_.pop(), struct_def.name); 36 | } 37 | 38 | void AstCompare::enum_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs) { 39 | record(cero::AstNodeKind::EnumDefinition); 40 | expected_access_.push(access); 41 | expected_name_.push(name); 42 | 43 | visit_children(cs); 44 | } 45 | 46 | void AstCompare::visit(const cero::AstEnumDefinition& enum_def) { 47 | expect(enum_def); 48 | CHECK_EQ(expected_access_.pop(), enum_def.access); 49 | CHECK_EQ(expected_name_.pop(), enum_def.name); 50 | } 51 | 52 | void AstCompare::function_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs) { 53 | record(cero::AstNodeKind::FunctionDefinition); 54 | expected_access_.push(access); 55 | expected_name_.push(name); 56 | 57 | visit_children(cs); 58 | } 59 | 60 | void AstCompare::visit(const cero::AstFunctionDefinition& func_def) { 61 | expect(func_def); 62 | CHECK_EQ(expected_access_.pop(), func_def.access); 63 | CHECK_EQ(expected_name_.pop(), func_def.name); 64 | } 65 | 66 | void AstCompare::function_parameter(cero::ParameterSpecifier specifier, std::string_view name, ChildScope cs) { 67 | record(cero::AstNodeKind::FunctionParameter); 68 | expected_param_specifier_.push(specifier); 69 | expected_name_.push(name); 70 | 71 | visit_children(cs); 72 | } 73 | 74 | void AstCompare::visit(const cero::AstFunctionParameter& param) { 75 | expect(param); 76 | CHECK_EQ(expected_param_specifier_.pop(), param.specifier); 77 | CHECK_EQ(expected_name_.pop(), param.name); 78 | } 79 | 80 | void AstCompare::function_output(std::string_view name, ChildScope cs) { 81 | record(cero::AstNodeKind::FunctionOutput); 82 | expected_name_.push(name); 83 | 84 | visit_children(cs); 85 | } 86 | 87 | void AstCompare::visit(const cero::AstFunctionOutput& output) { 88 | expect(output); 89 | CHECK_EQ(expected_name_.pop(), output.name); 90 | } 91 | 92 | void AstCompare::block_statement(ChildScope cs) { 93 | record(cero::AstNodeKind::BlockStatement); 94 | 95 | visit_children(cs); 96 | } 97 | 98 | void AstCompare::visit(const cero::AstBlockStatement& block_stmt) { 99 | expect(block_stmt); 100 | } 101 | 102 | void AstCompare::binding_statement(cero::BindingSpecifier specifier, std::string_view name, ChildScope cs) { 103 | record(cero::AstNodeKind::BindingStatement); 104 | expected_binding_specifier_.push(specifier); 105 | expected_name_.push(name); 106 | 107 | visit_children(cs); 108 | } 109 | 110 | void AstCompare::visit(const cero::AstBindingStatement& binding) { 111 | expect(binding); 112 | CHECK_EQ(expected_binding_specifier_.pop(), binding.specifier); 113 | CHECK_EQ(expected_name_.pop(), binding.name); 114 | } 115 | 116 | void AstCompare::if_expr(ChildScope cs) { 117 | record(cero::AstNodeKind::IfExpr); 118 | 119 | visit_children(cs); 120 | } 121 | 122 | void AstCompare::visit(const cero::AstIfExpr& if_stmt) { 123 | expect(if_stmt); 124 | } 125 | 126 | void AstCompare::while_loop(ChildScope cs) { 127 | record(cero::AstNodeKind::WhileLoop); 128 | 129 | visit_children(cs); 130 | } 131 | 132 | void AstCompare::visit(const cero::AstWhileLoop& while_loop) { 133 | expect(while_loop); 134 | } 135 | 136 | void AstCompare::visit(const cero::AstForLoop& for_loop) { 137 | expect(for_loop); 138 | } 139 | 140 | void AstCompare::name_expr(std::string_view name) { 141 | record(cero::AstNodeKind::NameExpr); 142 | expected_name_.push(name); 143 | 144 | visit_children(nullptr); 145 | } 146 | 147 | void AstCompare::visit(const cero::AstNameExpr& name_expr) { 148 | expect(name_expr); 149 | CHECK_EQ(expected_name_.pop(), name_expr.name); 150 | } 151 | 152 | void AstCompare::generic_name_expr(std::string_view name, ChildScope cs) { 153 | record(cero::AstNodeKind::GenericNameExpr); 154 | expected_name_.push(name); 155 | 156 | visit_children(cs); 157 | } 158 | 159 | void AstCompare::visit(const cero::AstGenericNameExpr& generic_name_expr) { 160 | expect(generic_name_expr); 161 | CHECK_EQ(expected_name_.pop(), generic_name_expr.name); 162 | } 163 | 164 | void AstCompare::member_expr(std::string_view name) { 165 | record(cero::AstNodeKind::MemberExpr); 166 | expected_name_.push(name); 167 | 168 | visit_children(nullptr); 169 | } 170 | 171 | void AstCompare::visit(const cero::AstMemberExpr& member_expr) { 172 | expect(member_expr); 173 | CHECK_EQ(expected_name_.pop(), member_expr.member); 174 | } 175 | 176 | void AstCompare::group_expr(ChildScope cs) { 177 | record(cero::AstNodeKind::GroupExpr); 178 | 179 | visit_children(cs); 180 | } 181 | 182 | void AstCompare::visit(const cero::AstGroupExpr& group_expr) { 183 | expect(group_expr); 184 | } 185 | 186 | void AstCompare::call_expr(ChildScope cs) { 187 | record(cero::AstNodeKind::CallExpr); 188 | 189 | visit_children(cs); 190 | } 191 | 192 | void AstCompare::visit(const cero::AstCallExpr& call_expr) { 193 | expect(call_expr); 194 | } 195 | 196 | void AstCompare::visit(const cero::AstIndexExpr& index_expr) { 197 | expect(index_expr); 198 | } 199 | 200 | void AstCompare::visit(const cero::AstArrayLiteralExpr& array_literal) { 201 | expect(array_literal); 202 | } 203 | 204 | void AstCompare::unary_expr(cero::UnaryOperator op, ChildScope cs) { 205 | record(cero::AstNodeKind::UnaryExpr); 206 | expected_unary_op_.push(op); 207 | 208 | visit_children(cs); 209 | } 210 | 211 | void AstCompare::visit(const cero::AstUnaryExpr& unary_expr) { 212 | expect(unary_expr); 213 | CHECK_EQ(expected_unary_op_.pop(), unary_expr.op); 214 | } 215 | 216 | void AstCompare::binary_expr(cero::BinaryOperator op, ChildScope cs) { 217 | record(cero::AstNodeKind::BinaryExpr); 218 | expected_binary_op_.push(op); 219 | 220 | visit_children(cs); 221 | } 222 | 223 | void AstCompare::visit(const cero::AstBinaryExpr& binary_expr) { 224 | expect(binary_expr); 225 | CHECK_EQ(expected_binary_op_.pop(), binary_expr.op); 226 | } 227 | 228 | void AstCompare::return_expr(ChildScope cs) { 229 | record(cero::AstNodeKind::ReturnExpr); 230 | 231 | visit_children(cs); 232 | } 233 | 234 | void AstCompare::visit(const cero::AstReturnExpr& return_expr) { 235 | expect(return_expr); 236 | } 237 | 238 | void AstCompare::visit(const cero::AstThrowExpr& throw_expr) { 239 | expect(throw_expr); 240 | } 241 | 242 | void AstCompare::visit(const cero::AstBreakExpr& break_expr) { 243 | expect(break_expr); 244 | } 245 | 246 | void AstCompare::visit(const cero::AstContinueExpr& continue_expr) { 247 | expect(continue_expr); 248 | } 249 | 250 | void AstCompare::numeric_literal_expr(cero::NumericLiteralKind kind) { 251 | record(cero::AstNodeKind::NumericLiteralExpr); 252 | expected_numeric_literal_kind_.push(kind); 253 | 254 | visit_children(nullptr); 255 | } 256 | 257 | void AstCompare::visit(const cero::AstNumericLiteralExpr& numeric_literal) { 258 | expect(numeric_literal); 259 | CHECK_EQ(expected_numeric_literal_kind_.pop(), numeric_literal.kind); 260 | } 261 | 262 | void AstCompare::visit(const cero::AstStringLiteralExpr& string_literal) { 263 | expect(string_literal); 264 | CHECK_EQ(expected_string_literal_.pop(), string_literal.value); 265 | } 266 | 267 | void AstCompare::visit(const cero::AstPermissionExpr& permission) { 268 | expect(permission); 269 | CHECK_EQ(expected_permission_specifier_.pop(), permission.specifier); 270 | } 271 | 272 | void AstCompare::visit(const cero::AstPointerTypeExpr& ptr_type) { 273 | expect(ptr_type); 274 | } 275 | 276 | void AstCompare::visit(const cero::AstArrayTypeExpr& array_type) { 277 | expect(array_type); 278 | } 279 | 280 | void AstCompare::visit(const cero::AstFunctionTypeExpr& func_type) { 281 | expect(func_type); 282 | } 283 | 284 | void AstCompare::record(cero::AstNodeKind kind) { 285 | expected_kind_.push(kind); 286 | ++current_child_count_; 287 | } 288 | 289 | void AstCompare::visit_children(ChildScope child_scope) { 290 | cursor_.visit_one(*this); 291 | 292 | uint32_t expected_child_count = expected_child_count_.pop(); 293 | uint32_t old_current_count = std::exchange(current_child_count_, 0); 294 | 295 | if (child_scope != nullptr) { 296 | child_scope(*this); 297 | } 298 | 299 | CHECK_EQ(current_child_count_, expected_child_count); 300 | current_child_count_ = old_current_count; 301 | } 302 | 303 | template 304 | void AstCompare::expect(const N& node) { 305 | CHECK_EQ(expected_kind_.pop(), node.header.kind); 306 | 307 | expected_child_count_.push(node.num_children()); 308 | } 309 | 310 | } // namespace tests 311 | -------------------------------------------------------------------------------- /tests/syntax/AstCompare.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace tests { 9 | 10 | class AstCompare : public cero::AstVisitor { 11 | public: 12 | using ChildScope = void (*)(AstCompare&); 13 | 14 | explicit AstCompare(const cero::Ast& ast); 15 | ~AstCompare() override; 16 | 17 | void struct_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs); 18 | void enum_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs); 19 | void function_definition(cero::AccessSpecifier access, std::string_view name, ChildScope cs); 20 | void function_parameter(cero::ParameterSpecifier specifier, std::string_view name, ChildScope cs); 21 | void function_output(std::string_view name, ChildScope cs); 22 | void block_statement(ChildScope cs); 23 | void binding_statement(cero::BindingSpecifier specifier, std::string_view name, ChildScope cs); 24 | void if_expr(ChildScope cs); 25 | void while_loop(ChildScope cs); 26 | void name_expr(std::string_view name); 27 | void generic_name_expr(std::string_view name, ChildScope cs); 28 | void member_expr(std::string_view name); 29 | void group_expr(ChildScope cs); 30 | void call_expr(ChildScope cs); 31 | void unary_expr(cero::UnaryOperator op, ChildScope cs); 32 | void binary_expr(cero::BinaryOperator op, ChildScope cs); 33 | void return_expr(ChildScope cs); 34 | void numeric_literal_expr(cero::NumericLiteralKind kind); 35 | 36 | AstCompare(AstCompare&&) = delete; 37 | 38 | private: 39 | template 40 | class RecordState { 41 | public: 42 | RecordState() = default; 43 | RecordState(RecordState&&) = delete; 44 | 45 | ~RecordState() { 46 | cero_assert(!value_.has_value(), "test is misconstructed if this fails"); 47 | } 48 | 49 | void push(T val) { 50 | cero_assert(!value_.has_value(), "test is misconstructed if this fails"); 51 | value_.emplace(std::move(val)); 52 | } 53 | 54 | T pop() { 55 | cero_assert(value_.has_value(), "test is misconstructed if this fails"); 56 | return *std::exchange(value_, std::nullopt); 57 | } 58 | 59 | private: 60 | std::optional value_; 61 | }; 62 | 63 | cero::AstCursor cursor_; 64 | uint32_t current_child_count_ = 0; 65 | uint32_t root_count_ = 0; 66 | RecordState expected_child_count_; 67 | RecordState expected_kind_; 68 | RecordState expected_access_; 69 | RecordState expected_name_; 70 | RecordState expected_param_specifier_; 71 | RecordState expected_binding_specifier_; 72 | RecordState expected_unary_op_; 73 | RecordState expected_binary_op_; 74 | RecordState expected_numeric_literal_kind_; 75 | RecordState expected_string_literal_; 76 | RecordState expected_permission_specifier_; 77 | 78 | void visit(const cero::AstRoot& root) override; 79 | void visit(const cero::AstStructDefinition& struct_def) override; 80 | void visit(const cero::AstEnumDefinition& enum_def) override; 81 | void visit(const cero::AstFunctionDefinition& func_def) override; 82 | void visit(const cero::AstFunctionParameter& param) override; 83 | void visit(const cero::AstFunctionOutput& output) override; 84 | void visit(const cero::AstBlockStatement& block_stmt) override; 85 | void visit(const cero::AstBindingStatement& binding) override; 86 | void visit(const cero::AstIfExpr& if_stmt) override; 87 | void visit(const cero::AstWhileLoop& while_loop) override; 88 | void visit(const cero::AstForLoop& for_loop) override; 89 | void visit(const cero::AstNameExpr& name_expr) override; 90 | void visit(const cero::AstGenericNameExpr& generic_name_expr) override; 91 | void visit(const cero::AstMemberExpr& member_expr) override; 92 | void visit(const cero::AstGroupExpr& group_expr) override; 93 | void visit(const cero::AstCallExpr& call_expr) override; 94 | void visit(const cero::AstIndexExpr& index_expr) override; 95 | void visit(const cero::AstArrayLiteralExpr& array_literal) override; 96 | void visit(const cero::AstUnaryExpr& unary_expr) override; 97 | void visit(const cero::AstBinaryExpr& binary_expr) override; 98 | void visit(const cero::AstReturnExpr& return_expr) override; 99 | void visit(const cero::AstThrowExpr& throw_expr) override; 100 | void visit(const cero::AstBreakExpr& break_expr) override; 101 | void visit(const cero::AstContinueExpr& continue_expr) override; 102 | void visit(const cero::AstNumericLiteralExpr& numeric_literal) override; 103 | void visit(const cero::AstStringLiteralExpr& string_literal) override; 104 | void visit(const cero::AstPermissionExpr& permission) override; 105 | void visit(const cero::AstPointerTypeExpr& ptr_type) override; 106 | void visit(const cero::AstArrayTypeExpr& array_type) override; 107 | void visit(const cero::AstFunctionTypeExpr& func_type) override; 108 | 109 | void record(cero::AstNodeKind kind); 110 | void visit_children(ChildScope child_scope); 111 | 112 | template 113 | void expect(const N& node); 114 | }; 115 | 116 | } // namespace tests 117 | -------------------------------------------------------------------------------- /tests/syntax/AstStringResults.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | #include 5 | 6 | namespace tests { 7 | 8 | CERO_TEST(AstStringForEmptyFunction) { 9 | auto source = make_test_source(R"_____( 10 | 11 | main() { 12 | } 13 | 14 | )_____"); 15 | 16 | ExhaustiveReporter r; 17 | auto ast = cero::run_parser_on_source(source, r, TabSize); 18 | CHECK(!ast.has_errors()); 19 | 20 | auto str = ast.to_string(source, TabSize); 21 | auto expected = R"_____(AST for AstStringForEmptyFunction (2 nodes) 22 | └── function `main` [3:1] 23 | ├── parameters 24 | ├── outputs 25 | └── statements 26 | )_____"; 27 | CHECK_EQ(str, expected); 28 | } 29 | 30 | CERO_TEST(AstStringForSimpleFunctionWithParametersAndReturn) { 31 | auto source = make_test_source(R"_____( 32 | 33 | a(int32 x, bool _a, bool _b = x) -> float32 { 34 | } 35 | 36 | )_____"); 37 | 38 | ExhaustiveReporter r; 39 | auto ast = cero::run_parser_on_source(source, r, TabSize); 40 | CHECK(!ast.has_errors()); 41 | 42 | auto str = ast.to_string(source, TabSize); 43 | auto expected = R"_____(AST for AstStringForSimpleFunctionWithParametersAndReturn (11 nodes) 44 | └── function `a` [3:1] 45 | ├── parameters 46 | │ ├── value parameter `x` [3:3] 47 | │ │ └── name `int32` [3:3] 48 | │ ├── value parameter `_a` [3:12] 49 | │ │ └── name `bool` [3:12] 50 | │ └── value parameter `_b` [3:21] 51 | │ ├── name `bool` [3:21] 52 | │ └── name `x` [3:31] 53 | ├── outputs 54 | │ └── output [3:37] 55 | │ └── name `float32` [3:37] 56 | └── statements 57 | )_____"; 58 | CHECK_EQ(str, expected); 59 | } 60 | 61 | CERO_TEST(AstStringForCall) { 62 | auto source = make_test_source(R"_____( 63 | 64 | a(int32 _a, float64 _f, int64 _b) { 65 | } 66 | 67 | b(int32 i, float64 f) { 68 | a(i, f, i * i); 69 | } 70 | 71 | )_____"); 72 | 73 | ExhaustiveReporter r; 74 | auto ast = cero::run_parser_on_source(source, r, TabSize); 75 | CHECK(!ast.has_errors()); 76 | 77 | auto str = ast.to_string(source, TabSize); 78 | auto expected = R"_____(AST for AstStringForCall (20 nodes) 79 | ├── function `a` [3:1] 80 | │ ├── parameters 81 | │ │ ├── value parameter `_a` [3:3] 82 | │ │ │ └── name `int32` [3:3] 83 | │ │ ├── value parameter `_f` [3:13] 84 | │ │ │ └── name `float64` [3:13] 85 | │ │ └── value parameter `_b` [3:25] 86 | │ │ └── name `int64` [3:25] 87 | │ ├── outputs 88 | │ └── statements 89 | └── function `b` [6:1] 90 | ├── parameters 91 | │ ├── value parameter `i` [6:3] 92 | │ │ └── name `int32` [6:3] 93 | │ └── value parameter `f` [6:12] 94 | │ └── name `float64` [6:12] 95 | ├── outputs 96 | └── statements 97 | └── call expression [7:5] 98 | ├── name `a` [7:5] 99 | └── arguments 100 | ├── name `i` [7:7] 101 | ├── name `f` [7:10] 102 | └── `*` [7:13] 103 | ├── name `i` [7:13] 104 | └── name `i` [7:17] 105 | )_____"; 106 | CHECK_EQ(str, expected); 107 | } 108 | 109 | CERO_TEST(AstStringForFibonacci) { 110 | auto source = make_test_source(R"_____( 111 | fibonacci(var uint32 n) -> uint32 { 112 | var uint32 result = 0; 113 | var uint32 next = 1; 114 | 115 | while n-- != 0 { 116 | let temp = next; 117 | next = result; 118 | result += temp; 119 | } 120 | 121 | return result; 122 | } 123 | )_____"); 124 | 125 | ExhaustiveReporter r; 126 | auto ast = cero::run_parser_on_source(source, r, TabSize); 127 | CHECK(!ast.has_errors()); 128 | 129 | auto str = ast.to_string(source, TabSize); 130 | auto expected = R"_____(AST for AstStringForFibonacci (27 nodes) 131 | └── function `fibonacci` [2:1] 132 | ├── parameters 133 | │ └── var parameter `n` [2:11] 134 | │ └── name `uint32` [2:15] 135 | ├── outputs 136 | │ └── output [2:28] 137 | │ └── name `uint32` [2:28] 138 | └── statements 139 | ├── var binding `result` [3:5] 140 | │ ├── type 141 | │ │ └── name `uint32` [3:9] 142 | │ └── initializer 143 | │ └── decimal literal ` ---TODO--- ` [3:25] 144 | ├── var binding `next` [4:5] 145 | │ ├── type 146 | │ │ └── name `uint32` [4:9] 147 | │ └── initializer 148 | │ └── decimal literal ` ---TODO--- ` [4:25] 149 | ├── while [6:5] 150 | │ ├── `!=` [6:11] 151 | │ │ ├── `postfix --` [6:11] 152 | │ │ │ └── name `n` [6:11] 153 | │ │ └── decimal literal ` ---TODO--- ` [6:18] 154 | │ └── statements 155 | │ ├── let binding `temp` [7:9] 156 | │ │ └── initializer 157 | │ │ └── name `next` [7:20] 158 | │ ├── `=` [8:9] 159 | │ │ ├── name `next` [8:9] 160 | │ │ └── name `result` [8:16] 161 | │ └── `+=` [9:9] 162 | │ ├── name `result` [9:9] 163 | │ └── name `temp` [9:19] 164 | └── return [12:5] 165 | └── name `result` [12:12] 166 | )_____"; 167 | CHECK_EQ(str, expected); 168 | } 169 | 170 | } // namespace tests 171 | -------------------------------------------------------------------------------- /tests/syntax/LexerErrors.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | namespace tests { 5 | 6 | CERO_TEST(SourceTooLarge) { 7 | ExhaustiveReporter r; 8 | r.expect(0, 0, cero::Message::SourceFileTooLarge, cero::MessageArgs(cero::MaxSourceLength)); 9 | build_test_source(r, std::string(cero::MaxSourceLength + 1, ' ')); 10 | } 11 | 12 | CERO_TEST(InvalidCharacter) { 13 | ExhaustiveReporter r; 14 | r.expect(5, 1, cero::Message::InvalidCharacter, cero::MessageArgs(0x7)); 15 | build_test_source(r, R"_____( 16 | main() { 17 | } 18 | 19 | () { 20 | } 21 | )_____"); 22 | } 23 | 24 | CERO_TEST(MissingClosingQuote) { 25 | ExhaustiveReporter r; 26 | r.expect(3, 28, cero::Message::MissingClosingQuote, cero::MessageArgs()); 27 | r.expect(5, 17, cero::Message::MissingClosingQuote, cero::MessageArgs()); 28 | build_test_source(r, R"_____( 29 | foo() { 30 | let string = "Oh no...; 31 | ; 32 | let ch = 'x; 33 | ; 34 | } 35 | )_____"); 36 | } 37 | 38 | CERO_TEST(UnterminatedBlockComment) { 39 | ExhaustiveReporter r; 40 | r.expect(2, 1, cero::Message::UnterminatedBlockComment, cero::MessageArgs()); 41 | build_test_source(r, R"_____( 42 | /* abc 43 | )_____"); 44 | } 45 | 46 | } // namespace tests 47 | -------------------------------------------------------------------------------- /tests/syntax/LexerResults.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace tests { 8 | 9 | static void check_token_kinds(const cero::TokenStream& token_stream, std::initializer_list kinds) { 10 | REQUIRE(token_stream.num_tokens() == kinds.size()); 11 | 12 | cero::TokenCursor c(token_stream); 13 | for (auto kind : kinds) { 14 | CHECK_EQ(kind, c.next().kind); 15 | } 16 | } 17 | 18 | static std::string_view next_lexeme(cero::TokenCursor& cursor, const cero::SourceGuard& source) { 19 | auto lexeme = cursor.get_lexeme(source); 20 | cursor.advance(); 21 | return lexeme; 22 | } 23 | 24 | using enum cero::TokenKind; 25 | 26 | CERO_TEST(LexEmptySource) { 27 | auto source = make_test_source(""); 28 | 29 | ExhaustiveReporter r; 30 | auto tokens = cero::run_lexer(source, r, true, TabSize); 31 | CHECK(!tokens.has_errors()); 32 | 33 | check_token_kinds(tokens, {EndOfFile}); 34 | } 35 | 36 | CERO_TEST(LexIntegerLiterals) { 37 | auto source = make_test_source(R"_____( 38 | 0; 39 | 123; 40 | 123 456; 41 | 1234 5678; 42 | 0x123 456 eaeAEB234 32 B; 43 | 0x AB3235; 44 | 0x AB3235i; 45 | 0x 29356237 kk; 46 | 0b010110111; 47 | 0b 0110 11101 110; 48 | 0o1125417245; 49 | 0o 124 22115 2736; 50 | )_____"); 51 | 52 | ExhaustiveReporter r; 53 | auto tokens = cero::run_lexer(source, r, true, TabSize); 54 | CHECK(!tokens.has_errors()); 55 | 56 | check_token_kinds(tokens, 57 | {DecIntLiteral, Semicolon, DecIntLiteral, Semicolon, DecIntLiteral, Semicolon, DecIntLiteral, 58 | Semicolon, HexIntLiteral, Semicolon, HexIntLiteral, Semicolon, HexIntLiteral, Name, 59 | Semicolon, HexIntLiteral, Name, Semicolon, BinIntLiteral, Semicolon, BinIntLiteral, 60 | Semicolon, OctIntLiteral, Semicolon, OctIntLiteral, Semicolon, EndOfFile}); 61 | 62 | cero::TokenCursor c(tokens); 63 | CHECK_EQ(next_lexeme(c, source), "0"); 64 | CHECK_EQ(next_lexeme(c, source), ";"); 65 | CHECK_EQ(next_lexeme(c, source), "123"); 66 | CHECK_EQ(next_lexeme(c, source), ";"); 67 | CHECK_EQ(next_lexeme(c, source), "123 456"); 68 | CHECK_EQ(next_lexeme(c, source), ";"); 69 | CHECK_EQ(next_lexeme(c, source), "1234 5678"); 70 | CHECK_EQ(next_lexeme(c, source), ";"); 71 | CHECK_EQ(next_lexeme(c, source), "0x123 456 eaeAEB234 32 B"); 72 | CHECK_EQ(next_lexeme(c, source), ";"); 73 | CHECK_EQ(next_lexeme(c, source), "0x AB3235"); 74 | CHECK_EQ(next_lexeme(c, source), ";"); 75 | CHECK_EQ(next_lexeme(c, source), "0x AB3235"); 76 | CHECK_EQ(next_lexeme(c, source), "i"); 77 | CHECK_EQ(next_lexeme(c, source), ";"); 78 | CHECK_EQ(next_lexeme(c, source), "0x 29356237"); 79 | CHECK_EQ(next_lexeme(c, source), "kk"); 80 | CHECK_EQ(next_lexeme(c, source), ";"); 81 | CHECK_EQ(next_lexeme(c, source), "0b010110111"); 82 | CHECK_EQ(next_lexeme(c, source), ";"); 83 | CHECK_EQ(next_lexeme(c, source), "0b 0110 11101 110"); 84 | CHECK_EQ(next_lexeme(c, source), ";"); 85 | CHECK_EQ(next_lexeme(c, source), "0o1125417245"); 86 | CHECK_EQ(next_lexeme(c, source), ";"); 87 | CHECK_EQ(next_lexeme(c, source), "0o 124 22115 2736"); 88 | CHECK_EQ(next_lexeme(c, source), ";"); 89 | } 90 | 91 | CERO_TEST(LexFloatLiterals) { 92 | auto source = make_test_source(R"_____( 93 | 1.0; 94 | 1.; 95 | .4; 96 | .045; 97 | 100 000.000 231; 98 | 123 .456 7; 99 | 234 5 . 23 948; 100 | 1..z; 101 | 1.0.a; 102 | )_____"); 103 | 104 | ExhaustiveReporter r; 105 | auto tokens = cero::run_lexer(source, r, true, TabSize); 106 | CHECK(!tokens.has_errors()); 107 | 108 | check_token_kinds(tokens, {FloatLiteral, Semicolon, DecIntLiteral, Dot, Semicolon, FloatLiteral, Semicolon, 109 | FloatLiteral, Semicolon, FloatLiteral, Semicolon, FloatLiteral, Semicolon, FloatLiteral, 110 | Semicolon, DecIntLiteral, Dot, Dot, Name, Semicolon, FloatLiteral, 111 | Dot, Name, Semicolon, EndOfFile}); 112 | 113 | cero::TokenCursor c(tokens); 114 | CHECK_EQ(next_lexeme(c, source), "1.0"); 115 | CHECK_EQ(next_lexeme(c, source), ";"); 116 | CHECK_EQ(next_lexeme(c, source), "1"); 117 | CHECK_EQ(next_lexeme(c, source), "."); 118 | CHECK_EQ(next_lexeme(c, source), ";"); 119 | CHECK_EQ(next_lexeme(c, source), ".4"); 120 | CHECK_EQ(next_lexeme(c, source), ";"); 121 | CHECK_EQ(next_lexeme(c, source), ".045"); 122 | CHECK_EQ(next_lexeme(c, source), ";"); 123 | CHECK_EQ(next_lexeme(c, source), "100 000.000 231"); 124 | CHECK_EQ(next_lexeme(c, source), ";"); 125 | CHECK_EQ(next_lexeme(c, source), "123 .456 7"); 126 | CHECK_EQ(next_lexeme(c, source), ";"); 127 | CHECK_EQ(next_lexeme(c, source), "234 5 . 23 948"); 128 | CHECK_EQ(next_lexeme(c, source), ";"); 129 | CHECK_EQ(next_lexeme(c, source), "1"); 130 | CHECK_EQ(next_lexeme(c, source), "."); 131 | CHECK_EQ(next_lexeme(c, source), "."); 132 | CHECK_EQ(next_lexeme(c, source), "z"); 133 | CHECK_EQ(next_lexeme(c, source), ";"); 134 | CHECK_EQ(next_lexeme(c, source), "1.0"); 135 | CHECK_EQ(next_lexeme(c, source), "."); 136 | CHECK_EQ(next_lexeme(c, source), "a"); 137 | CHECK_EQ(next_lexeme(c, source), ";"); 138 | } 139 | 140 | CERO_TEST(LexStringLiteralsWithEscapes) { 141 | auto source = make_test_source(R"_____( 142 | "123\"" 143 | "\"" 144 | "" 145 | "\\" 146 | "\a" 147 | "\np" 148 | "\"\\a\a" 149 | "\"\\\"\\\\a\\a\"" 150 | )_____"); 151 | 152 | ExhaustiveReporter r; 153 | auto tokens = cero::run_lexer(source, r, true, TabSize); 154 | CHECK(!tokens.has_errors()); 155 | 156 | check_token_kinds(tokens, {StringLiteral, StringLiteral, StringLiteral, StringLiteral, StringLiteral, StringLiteral, 157 | StringLiteral, StringLiteral, EndOfFile}); 158 | 159 | cero::TokenCursor c(tokens); 160 | CHECK_EQ(next_lexeme(c, source), "\"123\\\"\""); 161 | CHECK_EQ(next_lexeme(c, source), "\"\\\"\""); 162 | CHECK_EQ(next_lexeme(c, source), "\"\""); 163 | CHECK_EQ(next_lexeme(c, source), "\"\\\\\""); 164 | CHECK_EQ(next_lexeme(c, source), "\"\\a\""); 165 | CHECK_EQ(next_lexeme(c, source), "\"\\np\""); 166 | CHECK_EQ(next_lexeme(c, source), "\"\\\"\\\\a\\a\""); 167 | CHECK_EQ(next_lexeme(c, source), "\"\\\"\\\\\\\"\\\\\\\\a\\\\a\\\"\""); 168 | } 169 | 170 | CERO_TEST(LexLineComments) { 171 | auto source = make_test_source(R"_____( 172 | // 173 | // 174 | // abc 175 | // // 176 | )_____"); 177 | 178 | ExhaustiveReporter r; 179 | auto tokens = cero::run_lexer(source, r, true, TabSize); 180 | CHECK(!tokens.has_errors()); 181 | 182 | check_token_kinds(tokens, {LineComment, LineComment, LineComment, LineComment, EndOfFile}); 183 | 184 | cero::TokenCursor c(tokens); 185 | CHECK_EQ(next_lexeme(c, source), "//"); 186 | CHECK_EQ(next_lexeme(c, source), "//"); 187 | CHECK_EQ(next_lexeme(c, source), "// abc"); 188 | CHECK_EQ(next_lexeme(c, source), "// //"); 189 | } 190 | 191 | CERO_TEST(LexBlockComments) { 192 | auto source = make_test_source(R"_____( 193 | /**/ 194 | /* abc 195 | */ 196 | /* 197 | 198 | 199 | */ 200 | /*/**/*/ 201 | /*a/*b*/c*/ 202 | /*/*/**/*/*/ 203 | /***/ 204 | /* **** */ 205 | /*/ */ 206 | /*// */ 207 | )_____"); 208 | 209 | ExhaustiveReporter r; 210 | auto tokens = cero::run_lexer(source, r, true, TabSize); 211 | CHECK(!tokens.has_errors()); 212 | 213 | check_token_kinds(tokens, {BlockComment, BlockComment, BlockComment, BlockComment, BlockComment, BlockComment, BlockComment, 214 | BlockComment, BlockComment, BlockComment, EndOfFile}); 215 | 216 | cero::TokenCursor c(tokens); 217 | CHECK_EQ(next_lexeme(c, source), "/**/"); 218 | CHECK_EQ(next_lexeme(c, source), "/* abc\n*/"); 219 | CHECK_EQ(next_lexeme(c, source), "/*\n\n\n*/"); 220 | CHECK_EQ(next_lexeme(c, source), "/*/**/*/"); 221 | CHECK_EQ(next_lexeme(c, source), "/*a/*b*/c*/"); 222 | CHECK_EQ(next_lexeme(c, source), "/*/*/**/*/*/"); 223 | CHECK_EQ(next_lexeme(c, source), "/***/"); 224 | CHECK_EQ(next_lexeme(c, source), "/* **** */"); 225 | CHECK_EQ(next_lexeme(c, source), "/*/ */"); 226 | CHECK_EQ(next_lexeme(c, source), "/*// */"); 227 | } 228 | 229 | CERO_TEST(LexBracketCaret) { 230 | auto source = make_test_source(R"_____( 231 | [^ 232 | )_____"); 233 | 234 | ExhaustiveReporter r; 235 | auto tokens = cero::run_lexer(source, r, true, TabSize); 236 | CHECK(!tokens.has_errors()); 237 | 238 | check_token_kinds(tokens, {LBracket, Caret, EndOfFile}); 239 | } 240 | 241 | CERO_TEST(LexUnicodeNames) { 242 | auto source = make_test_source(R"_____( 243 | 𖭽() 244 | {} 245 | )_____"); 246 | 247 | ExhaustiveReporter r; 248 | auto tokens = cero::run_lexer(source, r, true, TabSize); 249 | CHECK(!tokens.has_errors()); 250 | 251 | check_token_kinds(tokens, {Name, LParen, RParen, LBrace, RBrace, EndOfFile}); 252 | 253 | cero::TokenCursor c(tokens); 254 | CHECK_EQ(next_lexeme(c, source), "𖭽"); 255 | } 256 | 257 | CERO_TEST(LexOperators) { 258 | auto source = make_test_source(R"_____( 259 | ! 260 | + - * / % 261 | == != < > <= >= 262 | . 263 | :: 264 | && || 265 | = 266 | & | ~ << >> 267 | )_____"); 268 | 269 | ExhaustiveReporter r; 270 | auto tokens = cero::run_lexer(source, r, true, TabSize); 271 | CHECK(!tokens.has_errors()); 272 | 273 | check_token_kinds(tokens, {Bang, Plus, Minus, Star, Slash, Percent, EqEq, BangEq, 274 | LAngle, RAngle, LAngleEq, RAngleEq, Dot, ColonColon, AmpAmp, PipePipe, 275 | Eq, Amp, Pipe, Tilde, LAngleLAngle, RAngle, RAngle, EndOfFile}); 276 | } 277 | 278 | CERO_TEST(LexDotDot) { 279 | auto source = make_test_source(R"_____( 280 | .. 281 | )_____"); 282 | 283 | ExhaustiveReporter r; 284 | auto tokens = cero::run_lexer(source, r, true, TabSize); 285 | CHECK(!tokens.has_errors()); 286 | 287 | check_token_kinds(tokens, {Dot, Dot, EndOfFile}); 288 | } 289 | 290 | } // namespace tests 291 | -------------------------------------------------------------------------------- /tests/syntax/ParserErrors.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | namespace tests { 5 | 6 | CERO_TEST(ExpectFuncStructEnum) { 7 | ExhaustiveReporter r; 8 | r.expect(5, 1, cero::Message::ExpectFuncStructEnum, cero::MessageArgs("`(`")); 9 | 10 | build_test_source(r, R"_____( 11 | main() { 12 | } 13 | 14 | () { 15 | } 16 | 17 | foo() { 18 | } 19 | )_____"); 20 | } 21 | 22 | CERO_TEST(ExpectParenAfterFuncName) { 23 | ExhaustiveReporter r; 24 | r.expect(2, 5, cero::Message::ExpectParenAfterFuncName, cero::MessageArgs("`)`")); 25 | 26 | build_test_source(r, R"_____( 27 | main) { 28 | } 29 | )_____"); 30 | } 31 | 32 | CERO_TEST(MissingParameter) { 33 | ExhaustiveReporter r; 34 | r.expect(2, 5, cero::Message::ExpectType, cero::MessageArgs("`,`")); 35 | r.expect(2, 5, cero::Message::ExpectParamName, cero::MessageArgs("`,`")); 36 | 37 | build_test_source(r, R"_____( 38 | foo(, bool x) -> bool { 39 | return x; 40 | } 41 | )_____"); 42 | } 43 | 44 | CERO_TEST(MissingParameterName) { 45 | ExhaustiveReporter r; 46 | r.expect(2, 9, cero::Message::ExpectParamName, cero::MessageArgs("`,`")); 47 | 48 | build_test_source(r, R"_____( 49 | foo(bool, bool x) -> bool { 50 | return x; 51 | } 52 | )_____"); 53 | } 54 | 55 | CERO_TEST(MissingParameterWithUnexpectedToken) { 56 | ExhaustiveReporter r; 57 | r.expect(2, 5, cero::Message::ExpectType, cero::MessageArgs("`}`")); 58 | r.expect(2, 5, cero::Message::ExpectParamName, cero::MessageArgs("`}`")); 59 | r.expect(6, 21, cero::Message::ExpectType, cero::MessageArgs("`%`")); 60 | r.expect(6, 21, cero::Message::ExpectParamName, cero::MessageArgs("`%`")); 61 | 62 | build_test_source(r, R"_____( 63 | foo(}, bool x) -> bool { 64 | return x; 65 | } 66 | 67 | private goo(bool x, %) -> bool { 68 | return x; 69 | } 70 | )_____"); 71 | } 72 | 73 | CERO_TEST(MissingParenAfterParameters) { 74 | ExhaustiveReporter r; 75 | r.expect(2, 20, cero::Message::ExpectParenAfterParams, cero::MessageArgs("`->`")); 76 | r.expect(6, 19, cero::Message::ExpectParenAfterParams, cero::MessageArgs("`}`")); 77 | 78 | build_test_source(r, R"_____( 79 | private foo(bool x -> bool { 80 | return x; 81 | } 82 | 83 | private goo(bool x} -> bool { 84 | return x; 85 | } 86 | )_____"); 87 | } 88 | 89 | CERO_TEST(MissingBraceBeforeFuncBody) { 90 | ExhaustiveReporter r; 91 | r.expect(3, 5, cero::Message::ExpectBraceBeforeFuncBody, cero::MessageArgs("`return`")); 92 | r.expect(7, 1, cero::Message::ExpectBraceBeforeFuncBody, cero::MessageArgs("`}`")); 93 | r.expect(9, 14, cero::Message::ExpectBraceBeforeFuncBody, cero::MessageArgs("`-`")); 94 | 95 | build_test_source(r, R"_____( 96 | foo(bool x) -> bool 97 | return x; 98 | } 99 | 100 | public goo() -> void 101 | } 102 | 103 | public hoo() -< void { 104 | } 105 | )_____"); 106 | } 107 | 108 | CERO_TEST(MissingNameAfterLet) { 109 | ExhaustiveReporter r; 110 | r.expect(3, 9, cero::Message::ExpectNameAfterLet, cero::MessageArgs("`=`")); 111 | 112 | build_test_source(r, R"_____( 113 | main() { 114 | let = true; 115 | let i = 0; 116 | } 117 | )_____"); 118 | } 119 | 120 | CERO_TEST(MissingNameInDeclaration) { 121 | ExhaustiveReporter r; 122 | r.expect(4, 19, cero::Message::ExpectNameAfterDeclarationType, cero::MessageArgs("`=`")); 123 | r.expect(6, 19, cero::Message::ExpectNameAfterDeclarationType, cero::MessageArgs("`=`")); 124 | 125 | build_test_source(r, R"_____( 126 | main() { 127 | const bool x = true; 128 | const ^bool = &x; 129 | const ^bool p = &x; 130 | const ^bool = &x; 131 | } 132 | )_____"); 133 | } 134 | 135 | CERO_TEST(ExpectExpr) { 136 | ExhaustiveReporter r; 137 | r.expect(3, 5, cero::Message::ExpectExpr, cero::MessageArgs("`]`")); 138 | r.expect(8, 1, cero::Message::ExpectExpr, cero::MessageArgs("`}`")); 139 | r.expect(11, 5, cero::Message::ExpectExpr, cero::MessageArgs("`+=`")); 140 | 141 | build_test_source(r, R"_____( 142 | a() { 143 | ] 144 | } 145 | 146 | c() { 147 | foo( 148 | } 149 | 150 | b() { 151 | += x 152 | } 153 | 154 | foo() { 155 | } 156 | )_____"); 157 | } 158 | 159 | CERO_TEST(ExpectSemicolon) { 160 | ExhaustiveReporter r; 161 | r.expect(4, 1, cero::Message::ExpectSemicolon, cero::MessageArgs("`}`")); 162 | 163 | build_test_source(r, R"_____( 164 | a() { 165 | return 0 166 | } 167 | 168 | b() { 169 | return 0; 170 | } 171 | )_____"); 172 | } 173 | 174 | CERO_TEST(NameCannotAppearHere) { 175 | ExhaustiveReporter r; 176 | r.expect(3, 11, cero::Message::NameCannotAppearHere, cero::MessageArgs()); 177 | 178 | build_test_source(r, R"_____( 179 | c(int32 a, int32 b) { 180 | a + b c; 181 | } 182 | )_____"); 183 | } 184 | 185 | CERO_TEST(MissingNameAfterDot) { 186 | ExhaustiveReporter r; 187 | r.expect(3, 7, cero::Message::ExpectNameAfterDot, cero::MessageArgs("`;`")); 188 | r.expect(6, 7, cero::Message::ExpectNameAfterDot, cero::MessageArgs("`(`")); 189 | 190 | build_test_source(r, R"_____( 191 | f() { 192 | x.; 193 | var y = true; 194 | 195 | b.(); 196 | var z = 12; 197 | } 198 | )_____"); 199 | } 200 | 201 | CERO_TEST(MissingColonInIfExpression) { 202 | ExhaustiveReporter r; 203 | r.expect(3, 18, cero::Message::ExpectColonOrBlockInIfExpr, cero::MessageArgs("integer literal `0`")); 204 | 205 | build_test_source(r, R"_____( 206 | f(bool b) -> int32 { 207 | let x = if b 0 else 1; 208 | return x; 209 | } 210 | 211 | g(bool b) -> int32 { 212 | let x = if b: 0 else 1; 213 | return x; 214 | } 215 | )_____"); 216 | } 217 | 218 | CERO_TEST(ExpectBlockAfterIfCond) { 219 | ExhaustiveReporter r; 220 | r.expect(4, 9, cero::Message::ExpectBlockAfterIfCond, cero::MessageArgs("`return`")); 221 | 222 | build_test_source(r, R"_____( 223 | f(bool b) -> int32 { 224 | if b 225 | return 4; 226 | 227 | print(b); 228 | print(b); 229 | print(b); 230 | } 231 | )_____"); 232 | } 233 | 234 | CERO_TEST(ExpectBlockAfterElse) { 235 | ExhaustiveReporter r; 236 | r.expect(6, 9, cero::Message::ExpectBlockAfterElse, cero::MessageArgs("`return`")); 237 | 238 | build_test_source(r, R"_____( 239 | g(bool b) -> int32 { 240 | if b { 241 | return 4; 242 | } else 243 | return 5; 244 | } 245 | )_____"); 246 | } 247 | 248 | CERO_TEST(UnnecessaryColonBeforeBlock) { 249 | ExhaustiveReporter r; 250 | // TODO: fix the example so it's semantically valid 251 | r.expect(3, 16, cero::Message::UnnecessaryColonBeforeBlock, cero::MessageArgs()); 252 | 253 | build_test_source(r, R"_____( 254 | f(bool b) -> int32 { 255 | return if b: { h(); } else 5; 256 | } 257 | 258 | g(bool b) -> int32 { 259 | if b { 260 | return 4; 261 | } 262 | return 5; 263 | } 264 | )_____"); 265 | } 266 | 267 | CERO_TEST(ExpectElse) { 268 | ExhaustiveReporter r; 269 | r.expect(3, 19, cero::Message::ExpectElse, cero::MessageArgs("`;`")); 270 | 271 | build_test_source(r, R"_____( 272 | f(bool b) -> int32 { 273 | return if b: 5; 274 | } 275 | )_____"); 276 | } 277 | 278 | CERO_TEST(ExpectBlockAfterWhileCond) { 279 | ExhaustiveReporter r; 280 | r.expect(5, 19, cero::Message::ExpectBlockAfterWhileCond, cero::MessageArgs("`:`")); 281 | 282 | build_test_source(r, R"_____( 283 | foo(var uint32 n) -> uint32 { 284 | var uint32 x = 0; 285 | 286 | while n-- != 0: 287 | x += n**2 + n; 288 | 289 | return x; 290 | } 291 | )_____"); 292 | } 293 | 294 | CERO_TEST(MissingParenAfterGroupExpression) { 295 | ExhaustiveReporter r; 296 | r.expect(4, 1, cero::Message::ExpectClosingParen, cero::MessageArgs("`}`")); 297 | 298 | build_test_source(r, R"_____( 299 | f(bool a, bool b, bool c, bool d) -> bool { 300 | return (a || b) && (c || d 301 | } 302 | 303 | g(bool a, bool b, bool c, bool d) -> bool { 304 | return (a || b) && (c || d); 305 | } 306 | )_____"); 307 | } 308 | 309 | CERO_TEST(MissingParenAfterFunctionCall) { 310 | ExhaustiveReporter r; 311 | r.expect(7, 1, cero::Message::ExpectClosingParen, cero::MessageArgs("`}`")); 312 | 313 | build_test_source(r, R"_____( 314 | foo(int32 _) { 315 | } 316 | 317 | f() { 318 | foo(1 319 | } 320 | 321 | g() { 322 | foo(2); 323 | } 324 | )_____"); 325 | } 326 | 327 | CERO_TEST(MissingBracketAfterIndex) { 328 | ExhaustiveReporter r; 329 | r.expect(4, 1, cero::Message::ExpectBracketAfterIndex, cero::MessageArgs("`}`")); 330 | 331 | build_test_source(r, R"_____( 332 | f([4]int32 x) -> int32 { 333 | return x[0 334 | } 335 | 336 | g([4]int32 x) -> int32 { 337 | return x[0]; 338 | } 339 | )_____"); 340 | } 341 | 342 | CERO_TEST(MissingBracketAfterArrayBound) { 343 | ExhaustiveReporter r; 344 | r.expect(2, 6, cero::Message::ExpectBracketAfterArrayBound, cero::MessageArgs("name `int32`")); 345 | 346 | build_test_source(r, R"_____( 347 | f([4 int32 x) -> int32 { 348 | return x[0]; 349 | } 350 | 351 | g([4]int32 x) -> int32 { 352 | return x[0]; 353 | } 354 | )_____"); 355 | } 356 | 357 | CERO_TEST(ExpectBraceAfterPermission) { 358 | ExhaustiveReporter r; 359 | r.expect(2, 10, cero::Message::ExpectBraceAfterPermission, cero::MessageArgs("name `MyList`")); 360 | 361 | build_test_source(r, R"_____( 362 | f(^var{1 MyList l) -> int32 { 363 | let int32 p = l^[0]; 364 | return p; 365 | } 366 | 367 | g(^var{1} MyList p) -> int32 { 368 | let int32 p = l^[0]; 369 | return p; 370 | } 371 | )_____"); 372 | } 373 | 374 | CERO_TEST(ExpectArrowAfterFuncTypeParams) { 375 | ExhaustiveReporter r; 376 | r.expect(2, 12, cero::Message::ExpectArrowAfterFuncTypeParams, cero::MessageArgs("name `f`")); 377 | 378 | build_test_source(r, R"_____( 379 | a(^(int32) f) -> int32 { 380 | return f(); 381 | } 382 | )_____"); 383 | } 384 | 385 | // TODO: add check for this in expression context 386 | CERO_TEST(FuncTypeDefaultArgument) { 387 | ExhaustiveReporter r; 388 | r.expect(2, 13, cero::Message::FuncTypeDefaultArgument, cero::MessageArgs()); 389 | 390 | build_test_source(r, R"_____( 391 | a(^(int32 x = 0)->int32 f) -> int32 { 392 | return f(3); 393 | } 394 | )_____"); 395 | } 396 | 397 | CERO_TEST(AmbiguousOperatorMixing) { 398 | ExhaustiveReporter r; 399 | r.expect(3, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs(">", "==")); 400 | r.expect(3, 24, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("==", "<")); 401 | r.expect(4, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("<", "==")); 402 | r.expect(4, 24, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("==", ">")); 403 | r.expect(5, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("&", "+")); 404 | r.expect(5, 28, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("&", "*")); 405 | r.expect(6, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("-", "|")); 406 | r.expect(6, 32, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("/", "|")); 407 | r.expect(7, 30, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("&&", "||")); 408 | r.expect(8, 30, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("||", "&&")); 409 | r.expect(9, 30, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("&&", "||")); 410 | r.expect(9, 40, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("||", "&&")); 411 | r.expect(10, 30, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("||", "&&")); 412 | r.expect(10, 40, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("&&", "||")); 413 | r.expect(11, 15, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("-", "**")); 414 | r.expect(12, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("+", ">>")); 415 | 416 | build_test_source(r, R"_____( 417 | f(float32 a, float32 b, int32 c, int32 d) { 418 | let e = a > b == c < d; 419 | let f = a < b == c > d; 420 | let g = c & d + c == c & d * c; 421 | let h = c - d | c == c / d | c; 422 | let i = a == b && c == d || a != d; 423 | let j = a == b || c == d && a != d; 424 | let k = a == b && c == d || a != d && b > c; 425 | let l = a == b || c == d && a != d || b > c; 426 | let m = -a**b; 427 | let n = a + b >> c; 428 | } 429 | )_____"); 430 | } 431 | 432 | CERO_TEST(IntransitiveOperatorMixing) { 433 | ExhaustiveReporter r; 434 | r.expect(4, 20, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("!=", "!=")); 435 | r.expect(5, 20, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("==", "!=")); 436 | r.expect(6, 20, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("!=", "==")); 437 | r.expect(8, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("<", ">")); 438 | r.expect(8, 23, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs(">", "<")); 439 | r.expect(12, 20, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs(">=", "!=")); 440 | r.expect(13, 19, cero::Message::AmbiguousOperatorMixing, cero::MessageArgs("<", "==")); 441 | 442 | build_test_source(r, R"_____( 443 | f(int32 a, int32 b, int32 c, int32 d) { 444 | let e = a == b == c == d; 445 | let f = a != b != c; 446 | let g = a == b != c; 447 | let h = a != b == c; 448 | let i = a < b < c < d; 449 | let j = a < b > c < d; 450 | let k = a > b > c; 451 | let l = a <= b <= c; 452 | let m = a >= b >= c >= d; 453 | let n = a >= b != c; 454 | let o = a < b == c; 455 | } 456 | )_____"); 457 | } 458 | 459 | } // namespace tests 460 | -------------------------------------------------------------------------------- /tests/syntax/ParserResultsControlFlow.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCompare.hpp" 2 | #include "common/ExhaustiveReporter.hpp" 3 | #include "common/Test.hpp" 4 | 5 | #include 6 | 7 | namespace tests { 8 | 9 | CERO_TEST(ParseWhileLoopFibonacci) { 10 | auto source = make_test_source(R"_____( 11 | fibonacci(var uint32 n) -> uint32 { 12 | var uint32 result = 0; 13 | var uint32 next = 1; 14 | 15 | while n-- != 0 { 16 | let temp = next; 17 | next = result; 18 | result += temp; 19 | } 20 | 21 | return result; 22 | } 23 | )_____"); 24 | 25 | ExhaustiveReporter r; 26 | auto ast = cero::run_parser_on_source(source, r, TabSize); 27 | CHECK(!ast.has_errors()); 28 | 29 | AstCompare c(ast); 30 | c.function_definition(cero::AccessSpecifier::None, "fibonacci", [](AstCompare& c) { 31 | c.function_parameter(cero::ParameterSpecifier::Var, "n", [](AstCompare& c) { 32 | c.name_expr("uint32"); 33 | }); 34 | c.function_output("", [](AstCompare& c) { 35 | c.name_expr("uint32"); 36 | }); 37 | c.binding_statement(cero::BindingSpecifier::Var, "result", [](AstCompare& c) { 38 | c.name_expr("uint32"); 39 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 40 | }); 41 | c.binding_statement(cero::BindingSpecifier::Var, "next", [](AstCompare& c) { 42 | c.name_expr("uint32"); 43 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 44 | }); 45 | c.while_loop([](AstCompare& c) { 46 | c.binary_expr(cero::BinaryOperator::NotEq, [](AstCompare& c) { 47 | c.unary_expr(cero::UnaryOperator::PostDec, [](AstCompare& c) { 48 | c.name_expr("n"); 49 | }); 50 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 51 | }); 52 | c.binding_statement(cero::BindingSpecifier::Let, "temp", [](AstCompare& c) { 53 | c.name_expr("next"); 54 | }); 55 | c.binary_expr(cero::BinaryOperator::Assign, [](AstCompare& c) { 56 | c.name_expr("next"); 57 | c.name_expr("result"); 58 | }); 59 | c.binary_expr(cero::BinaryOperator::AddAssign, [](AstCompare& c) { 60 | c.name_expr("result"); 61 | c.name_expr("temp"); 62 | }); 63 | }); 64 | c.return_expr([](AstCompare& c) { 65 | c.name_expr("result"); 66 | }); 67 | }); 68 | } 69 | 70 | CERO_TEST(ParseIfStatement) { 71 | auto source = make_test_source(R"_____( 72 | public divChecked(int32 a, int32 b) -> Opt { 73 | if b == 0 { 74 | return null; 75 | } else { 76 | return a / b; 77 | } 78 | } 79 | )_____"); 80 | 81 | ExhaustiveReporter r; 82 | auto ast = cero::run_parser_on_source(source, r, TabSize); 83 | CHECK(!ast.has_errors()); 84 | 85 | AstCompare c(ast); 86 | c.function_definition(cero::AccessSpecifier::Public, "divChecked", [](AstCompare& c) { 87 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 88 | c.name_expr("int32"); 89 | }); 90 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 91 | c.name_expr("int32"); 92 | }); 93 | c.function_output("", [](AstCompare& c) { 94 | c.generic_name_expr("Opt", [](AstCompare& c) { 95 | c.name_expr("int32"); 96 | }); 97 | }); 98 | c.if_expr([](AstCompare& c) { 99 | c.binary_expr(cero::BinaryOperator::Eq, [](AstCompare& c) { 100 | c.name_expr("b"); 101 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 102 | }); 103 | c.return_expr([](AstCompare& c) { 104 | c.name_expr("null"); 105 | }); 106 | c.return_expr([](AstCompare& c) { 107 | c.binary_expr(cero::BinaryOperator::Div, [](AstCompare& c) { 108 | c.name_expr("a"); 109 | c.name_expr("b"); 110 | }); 111 | }); 112 | }); 113 | }); 114 | } 115 | 116 | } // namespace tests 117 | -------------------------------------------------------------------------------- /tests/syntax/ParserResultsDefinitions.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCompare.hpp" 2 | 3 | #include "common/ExhaustiveReporter.hpp" 4 | #include "common/Test.hpp" 5 | 6 | #include 7 | 8 | namespace tests { 9 | 10 | CERO_TEST(ParseEmptyFunction) { 11 | auto source = make_test_source(R"_____( 12 | main() { 13 | } 14 | )_____"); 15 | 16 | ExhaustiveReporter r; 17 | auto ast = cero::run_parser_on_source(source, r, TabSize); 18 | CHECK(!ast.has_errors()); 19 | 20 | AstCompare c(ast); 21 | c.function_definition(cero::AccessSpecifier::None, "main", [](AstCompare&) {}); 22 | } 23 | 24 | } // namespace tests 25 | -------------------------------------------------------------------------------- /tests/syntax/ParserResultsGenerics.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCompare.hpp" 2 | #include "common/ExhaustiveReporter.hpp" 3 | #include "common/Test.hpp" 4 | 5 | #include 6 | 7 | namespace tests { 8 | 9 | CERO_TEST(ParseGenericReturnType) { 10 | auto source = make_test_source(R"_____( 11 | a() -> List > { 12 | return (); 13 | } 14 | 15 | b() -> List> { 16 | return (); 17 | } 18 | 19 | c() -> List > > { 20 | return (); 21 | } 22 | 23 | d() -> List >> { 24 | return (); 25 | } 26 | 27 | e() -> List> > { 28 | return (); 29 | } 30 | 31 | f() -> List>> { 32 | return (); 33 | } 34 | )_____"); 35 | 36 | ExhaustiveReporter r; 37 | auto ast = cero::run_parser_on_source(source, r, TabSize); 38 | CHECK(!ast.has_errors()); 39 | 40 | AstCompare c(ast); 41 | c.function_definition(cero::AccessSpecifier::None, "a", [](AstCompare& c) { 42 | c.function_output("", [](AstCompare& c) { 43 | c.generic_name_expr("List", [](AstCompare& c) { 44 | c.generic_name_expr("List", [](AstCompare& c) { 45 | c.name_expr("int32"); 46 | }); 47 | }); 48 | }); 49 | c.return_expr([](AstCompare& c) { 50 | c.group_expr([](AstCompare&) {}); 51 | }); 52 | }); 53 | c.function_definition(cero::AccessSpecifier::None, "b", [](AstCompare& c) { 54 | c.function_output("", [](AstCompare& c) { 55 | c.generic_name_expr("List", [](AstCompare& c) { 56 | c.generic_name_expr("List", [](AstCompare& c) { 57 | c.name_expr("int32"); 58 | }); 59 | }); 60 | }); 61 | c.return_expr([](AstCompare& c) { 62 | c.group_expr([](AstCompare&) {}); 63 | }); 64 | }); 65 | c.function_definition(cero::AccessSpecifier::None, "c", [](AstCompare& c) { 66 | c.function_output("", [](AstCompare& c) { 67 | c.generic_name_expr("List", [](AstCompare& c) { 68 | c.generic_name_expr("List", [](AstCompare& c) { 69 | c.generic_name_expr("List", [](AstCompare& c) { 70 | c.name_expr("int32"); 71 | }); 72 | }); 73 | }); 74 | }); 75 | c.return_expr([](AstCompare& c) { 76 | c.group_expr([](AstCompare&) {}); 77 | }); 78 | }); 79 | c.function_definition(cero::AccessSpecifier::None, "d", [](AstCompare& c) { 80 | c.function_output("", [](AstCompare& c) { 81 | c.generic_name_expr("List", [](AstCompare& c) { 82 | c.generic_name_expr("List", [](AstCompare& c) { 83 | c.generic_name_expr("List", [](AstCompare& c) { 84 | c.name_expr("int32"); 85 | }); 86 | }); 87 | }); 88 | }); 89 | c.return_expr([](AstCompare& c) { 90 | c.group_expr([](AstCompare&) {}); 91 | }); 92 | }); 93 | c.function_definition(cero::AccessSpecifier::None, "e", [](AstCompare& c) { 94 | c.function_output("", [](AstCompare& c) { 95 | c.generic_name_expr("List", [](AstCompare& c) { 96 | c.generic_name_expr("List", [](AstCompare& c) { 97 | c.generic_name_expr("List", [](AstCompare& c) { 98 | c.name_expr("int32"); 99 | }); 100 | }); 101 | }); 102 | }); 103 | c.return_expr([](AstCompare& c) { 104 | c.group_expr([](AstCompare&) {}); 105 | }); 106 | }); 107 | c.function_definition(cero::AccessSpecifier::None, "f", [](AstCompare& c) { 108 | c.function_output("", [](AstCompare& c) { 109 | c.generic_name_expr("List", [](AstCompare& c) { 110 | c.generic_name_expr("List", [](AstCompare& c) { 111 | c.generic_name_expr("List", [](AstCompare& c) { 112 | c.name_expr("int32"); 113 | }); 114 | }); 115 | }); 116 | }); 117 | c.return_expr([](AstCompare& c) { 118 | c.group_expr([](AstCompare&) {}); 119 | }); 120 | }); 121 | } 122 | 123 | CERO_TEST(ParseLessAndRightShift) { 124 | auto source = make_test_source(R"_____( 125 | oof(int32 a, int32 b) -> bool { 126 | return a < b >> (16 - 4); 127 | } 128 | 129 | oog(int32 a, int32 b, int32 c) -> bool { 130 | return a < b >> c; 131 | } 132 | )_____"); 133 | 134 | ExhaustiveReporter r; 135 | auto ast = cero::run_parser_on_source(source, r, TabSize); 136 | CHECK(!ast.has_errors()); 137 | 138 | AstCompare c(ast); 139 | c.function_definition(cero::AccessSpecifier::None, "oof", [](AstCompare& c) { 140 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 141 | c.name_expr("int32"); 142 | }); 143 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 144 | c.name_expr("int32"); 145 | }); 146 | c.function_output("", [](AstCompare& c) { 147 | c.name_expr("bool"); 148 | }); 149 | c.return_expr([](AstCompare& c) { 150 | c.binary_expr(cero::BinaryOperator::Less, [](AstCompare& c) { 151 | c.name_expr("a"); 152 | c.binary_expr(cero::BinaryOperator::Shr, [](AstCompare& c) { 153 | c.name_expr("b"); 154 | c.group_expr([](AstCompare& c) { 155 | c.binary_expr(cero::BinaryOperator::Sub, [](AstCompare& c) { 156 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 157 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 158 | }); 159 | }); 160 | }); 161 | }); 162 | }); 163 | }); 164 | c.function_definition(cero::AccessSpecifier::None, "oog", [](AstCompare& c) { 165 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 166 | c.name_expr("int32"); 167 | }); 168 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 169 | c.name_expr("int32"); 170 | }); 171 | c.function_parameter(cero::ParameterSpecifier::None, "c", [](AstCompare& c) { 172 | c.name_expr("int32"); 173 | }); 174 | c.function_output("", [](AstCompare& c) { 175 | c.name_expr("bool"); 176 | }); 177 | c.return_expr([](AstCompare& c) { 178 | c.binary_expr(cero::BinaryOperator::Less, [](AstCompare& c) { 179 | c.name_expr("a"); 180 | c.binary_expr(cero::BinaryOperator::Shr, [](AstCompare& c) { 181 | c.name_expr("b"); 182 | c.name_expr("c"); 183 | }); 184 | }); 185 | }); 186 | }); 187 | } 188 | 189 | CERO_TEST(ParseAmbiguousGenericCallVsComparisonArguments) { 190 | auto source = make_test_source(R"_____( 191 | ouch(float32 e) -> float64 { 192 | return a(b(e)); 193 | } 194 | )_____"); 195 | 196 | ExhaustiveReporter r; 197 | auto ast = cero::run_parser_on_source(source, r, TabSize); 198 | CHECK(!ast.has_errors()); 199 | 200 | AstCompare c(ast); 201 | c.function_definition(cero::AccessSpecifier::None, "ouch", [](AstCompare& c) { 202 | c.function_parameter(cero::ParameterSpecifier::None, "e", [](AstCompare& c) { 203 | c.name_expr("float32"); 204 | }); 205 | c.function_output("", [](AstCompare& c) { 206 | c.name_expr("float64"); 207 | }); 208 | c.return_expr([](AstCompare& c) { 209 | c.call_expr([](AstCompare& c) { 210 | c.name_expr("a"); 211 | c.call_expr([](AstCompare& c) { 212 | c.generic_name_expr("b", [](AstCompare& c) { 213 | c.name_expr("c"); 214 | c.name_expr("d"); 215 | }); 216 | c.name_expr("e"); 217 | }); 218 | }); 219 | }); 220 | }); 221 | } 222 | 223 | CERO_TEST(ParseComparisonArgumentsVsGenericPattern) { 224 | auto source = make_test_source(R"_____( 225 | ouch(float32 e) -> float64 { 226 | return a(b < c, d > e); 227 | } 228 | )_____"); 229 | 230 | ExhaustiveReporter r; 231 | auto ast = cero::run_parser_on_source(source, r, TabSize); 232 | CHECK(!ast.has_errors()); 233 | 234 | AstCompare c(ast); 235 | c.function_definition(cero::AccessSpecifier::None, "ouch", [](AstCompare& c) { 236 | c.function_parameter(cero::ParameterSpecifier::None, "e", [](AstCompare& c) { 237 | c.name_expr("float32"); 238 | }); 239 | c.function_output("", [](AstCompare& c) { 240 | c.name_expr("float64"); 241 | }); 242 | c.return_expr([](AstCompare& c) { 243 | c.call_expr([](AstCompare& c) { 244 | c.name_expr("a"); 245 | c.binary_expr(cero::BinaryOperator::Less, [](AstCompare& c) { 246 | c.name_expr("b"); 247 | c.name_expr("c"); 248 | }); 249 | c.binary_expr(cero::BinaryOperator::Greater, [](AstCompare& c) { 250 | c.name_expr("d"); 251 | c.name_expr("e"); 252 | }); 253 | }); 254 | }); 255 | }); 256 | } 257 | 258 | CERO_TEST(ParseComparisonAndRightShiftAsGenericArgument) { 259 | auto source = make_test_source(R"_____( 260 | woof() -> A<(B > C)> { 261 | return (); 262 | } 263 | 264 | meow() -> A<(D >> E)> { 265 | return (); 266 | } 267 | )_____"); 268 | 269 | ExhaustiveReporter r; 270 | auto ast = cero::run_parser_on_source(source, r, TabSize); 271 | CHECK(!ast.has_errors()); 272 | 273 | AstCompare c(ast); 274 | c.function_definition(cero::AccessSpecifier::None, "woof", [](AstCompare& c) { 275 | c.function_output("", [](AstCompare& c) { 276 | c.generic_name_expr("A", [](AstCompare& c) { 277 | c.group_expr([](AstCompare& c) { 278 | c.binary_expr(cero::BinaryOperator::Greater, [](AstCompare& c) { 279 | c.name_expr("B"); 280 | c.name_expr("C"); 281 | }); 282 | }); 283 | }); 284 | }); 285 | c.return_expr([](AstCompare& c) { 286 | c.group_expr([](AstCompare&) {}); 287 | }); 288 | }); 289 | c.function_definition(cero::AccessSpecifier::None, "meow", [](AstCompare& c) { 290 | c.function_output("", [](AstCompare& c) { 291 | c.generic_name_expr("A", [](AstCompare& c) { 292 | c.group_expr([](AstCompare& c) { 293 | c.binary_expr(cero::BinaryOperator::Shr, [](AstCompare& c) { 294 | c.name_expr("D"); 295 | c.name_expr("E"); 296 | }); 297 | }); 298 | }); 299 | }); 300 | c.return_expr([](AstCompare& c) { 301 | c.group_expr([](AstCompare&) {}); 302 | }); 303 | }); 304 | } 305 | 306 | CERO_TEST(ParseGenericParameters) { 307 | auto source = make_test_source(R"_____( 308 | moo(List _a, 309 | List> _b, 310 | List > _c, 311 | List > > _d, 312 | List >> _e, 313 | List> > _f, 314 | List>> _g) { 315 | } 316 | )_____"); 317 | 318 | ExhaustiveReporter r; 319 | auto ast = cero::run_parser_on_source(source, r, TabSize); 320 | CHECK(!ast.has_errors()); 321 | 322 | AstCompare c(ast); 323 | c.function_definition(cero::AccessSpecifier::None, "moo", [](AstCompare& c) { 324 | c.function_parameter(cero::ParameterSpecifier::None, "_a", [](AstCompare& c) { 325 | c.generic_name_expr("List", [](AstCompare& c) { 326 | c.name_expr("int32"); 327 | }); 328 | }); 329 | c.function_parameter(cero::ParameterSpecifier::None, "_b", [](AstCompare& c) { 330 | c.generic_name_expr("List", [](AstCompare& c) { 331 | c.generic_name_expr("List", [](AstCompare& c) { 332 | c.name_expr("int32"); 333 | }); 334 | }); 335 | }); 336 | c.function_parameter(cero::ParameterSpecifier::None, "_c", [](AstCompare& c) { 337 | c.generic_name_expr("List", [](AstCompare& c) { 338 | c.generic_name_expr("List", [](AstCompare& c) { 339 | c.name_expr("int32"); 340 | }); 341 | }); 342 | }); 343 | c.function_parameter(cero::ParameterSpecifier::None, "_d", [](AstCompare& c) { 344 | c.generic_name_expr("List", [](AstCompare& c) { 345 | c.generic_name_expr("List", [](AstCompare& c) { 346 | c.generic_name_expr("List", [](AstCompare& c) { 347 | c.name_expr("int32"); 348 | }); 349 | }); 350 | }); 351 | }); 352 | c.function_parameter(cero::ParameterSpecifier::None, "_e", [](AstCompare& c) { 353 | c.generic_name_expr("List", [](AstCompare& c) { 354 | c.generic_name_expr("List", [](AstCompare& c) { 355 | c.generic_name_expr("List", [](AstCompare& c) { 356 | c.name_expr("int32"); 357 | }); 358 | }); 359 | }); 360 | }); 361 | c.function_parameter(cero::ParameterSpecifier::None, "_f", [](AstCompare& c) { 362 | c.generic_name_expr("List", [](AstCompare& c) { 363 | c.generic_name_expr("List", [](AstCompare& c) { 364 | c.generic_name_expr("List", [](AstCompare& c) { 365 | c.name_expr("int32"); 366 | }); 367 | }); 368 | }); 369 | }); 370 | c.function_parameter(cero::ParameterSpecifier::None, "_g", [](AstCompare& c) { 371 | c.generic_name_expr("List", [](AstCompare& c) { 372 | c.generic_name_expr("List", [](AstCompare& c) { 373 | c.generic_name_expr("List", [](AstCompare& c) { 374 | c.name_expr("int32"); 375 | }); 376 | }); 377 | }); 378 | }); 379 | }); 380 | } 381 | 382 | CERO_TEST(ParseVariableWithGenericType) { 383 | auto source = make_test_source(R"_____( 384 | bark() { 385 | List _a = (); 386 | List> _b = (); 387 | List > _c = (); 388 | List > > _d = (); 389 | List >> _e = (); 390 | List> > _f = (); 391 | List>> _g = (); 392 | } 393 | )_____"); 394 | 395 | ExhaustiveReporter r; 396 | auto ast = cero::run_parser_on_source(source, r, TabSize); 397 | CHECK(!ast.has_errors()); 398 | 399 | AstCompare c(ast); 400 | c.function_definition(cero::AccessSpecifier::None, "bark", [](AstCompare& c) { 401 | c.binding_statement(cero::BindingSpecifier::Let, "_a", [](AstCompare& c) { 402 | c.generic_name_expr("List", [](AstCompare& c) { 403 | c.name_expr("int32"); 404 | }); 405 | c.group_expr([](AstCompare&) {}); 406 | }); 407 | c.binding_statement(cero::BindingSpecifier::Let, "_b", [](AstCompare& c) { 408 | c.generic_name_expr("List", [](AstCompare& c) { 409 | c.generic_name_expr("List", [](AstCompare& c) { 410 | c.name_expr("int32"); 411 | }); 412 | }); 413 | c.group_expr([](AstCompare&) {}); 414 | }); 415 | c.binding_statement(cero::BindingSpecifier::Let, "_c", [](AstCompare& c) { 416 | c.generic_name_expr("List", [](AstCompare& c) { 417 | c.generic_name_expr("List", [](AstCompare& c) { 418 | c.name_expr("int32"); 419 | }); 420 | }); 421 | c.group_expr([](AstCompare&) {}); 422 | }); 423 | c.binding_statement(cero::BindingSpecifier::Let, "_d", [](AstCompare& c) { 424 | c.generic_name_expr("List", [](AstCompare& c) { 425 | c.generic_name_expr("List", [](AstCompare& c) { 426 | c.generic_name_expr("List", [](AstCompare& c) { 427 | c.name_expr("int32"); 428 | }); 429 | }); 430 | }); 431 | c.group_expr([](AstCompare&) {}); 432 | }); 433 | c.binding_statement(cero::BindingSpecifier::Let, "_e", [](AstCompare& c) { 434 | c.generic_name_expr("List", [](AstCompare& c) { 435 | c.generic_name_expr("List", [](AstCompare& c) { 436 | c.generic_name_expr("List", [](AstCompare& c) { 437 | c.name_expr("int32"); 438 | }); 439 | }); 440 | }); 441 | c.group_expr([](AstCompare&) {}); 442 | }); 443 | c.binding_statement(cero::BindingSpecifier::Let, "_f", [](AstCompare& c) { 444 | c.generic_name_expr("List", [](AstCompare& c) { 445 | c.generic_name_expr("List", [](AstCompare& c) { 446 | c.generic_name_expr("List", [](AstCompare& c) { 447 | c.name_expr("int32"); 448 | }); 449 | }); 450 | }); 451 | c.group_expr([](AstCompare&) {}); 452 | }); 453 | c.binding_statement(cero::BindingSpecifier::Let, "_g", [](AstCompare& c) { 454 | c.generic_name_expr("List", [](AstCompare& c) { 455 | c.generic_name_expr("List", [](AstCompare& c) { 456 | c.generic_name_expr("List", [](AstCompare& c) { 457 | c.name_expr("int32"); 458 | }); 459 | }); 460 | }); 461 | c.group_expr([](AstCompare&) {}); 462 | }); 463 | }); 464 | } 465 | 466 | } // namespace tests 467 | -------------------------------------------------------------------------------- /tests/syntax/ParserResultsOperators.cpp: -------------------------------------------------------------------------------- 1 | #include "AstCompare.hpp" 2 | #include "common/ExhaustiveReporter.hpp" 3 | #include "common/Test.hpp" 4 | 5 | #include 6 | 7 | namespace tests { 8 | 9 | CERO_TEST(ParseAdditiveAndMultiplicativeOperators) { 10 | auto source = make_test_source(R"_____( 11 | foo(int32 a, int32 b) -> int32 { 12 | let c = a + b; 13 | let d = a + b * c; 14 | let e = (d - a) / c; 15 | return e**2 * b; 16 | } 17 | )_____"); 18 | 19 | ExhaustiveReporter r; 20 | auto ast = cero::run_parser_on_source(source, r, TabSize); 21 | CHECK(!ast.has_errors()); 22 | 23 | AstCompare c(ast); 24 | c.function_definition(cero::AccessSpecifier::None, "foo", [](AstCompare& c) { 25 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 26 | c.name_expr("int32"); 27 | }); 28 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 29 | c.name_expr("int32"); 30 | }); 31 | c.function_output("", [](AstCompare& c) { 32 | c.name_expr("int32"); 33 | }); 34 | c.binding_statement(cero::BindingSpecifier::Let, "c", [](AstCompare& c) { 35 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 36 | c.name_expr("a"); 37 | c.name_expr("b"); 38 | }); 39 | }); 40 | c.binding_statement(cero::BindingSpecifier::Let, "d", [](AstCompare& c) { 41 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 42 | c.name_expr("a"); 43 | c.binary_expr(cero::BinaryOperator::Mul, [](AstCompare& c) { 44 | c.name_expr("b"); 45 | c.name_expr("c"); 46 | }); 47 | }); 48 | }); 49 | c.binding_statement(cero::BindingSpecifier::Let, "e", [](AstCompare& c) { 50 | c.binary_expr(cero::BinaryOperator::Div, [](AstCompare& c) { 51 | c.group_expr([](AstCompare& c) { 52 | c.binary_expr(cero::BinaryOperator::Sub, [](AstCompare& c) { 53 | c.name_expr("d"); 54 | c.name_expr("a"); 55 | }); 56 | }); 57 | c.name_expr("c"); 58 | }); 59 | }); 60 | c.return_expr([](AstCompare& c) { 61 | c.binary_expr(cero::BinaryOperator::Mul, [](AstCompare& c) { 62 | c.binary_expr(cero::BinaryOperator::Pow, [](AstCompare& c) { 63 | c.name_expr("e"); 64 | c.numeric_literal_expr(cero::NumericLiteralKind::Decimal); 65 | }); 66 | c.name_expr("b"); 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | CERO_TEST(ParseAdditiveAndComparisonOperators) { 73 | auto source = make_test_source(R"_____( 74 | bar(int32 a, int32 b, int32 c) -> bool { 75 | let u = a - b == b + c; 76 | let v = b * a != c / a; 77 | let w = c + b > b * a; 78 | let x = b / a < c - b; 79 | let y = a * c <= b - a; 80 | let z = b + c >= a / c; 81 | return u || v || w || x || y || z; 82 | } 83 | )_____"); 84 | 85 | ExhaustiveReporter r; 86 | auto ast = cero::run_parser_on_source(source, r, TabSize); 87 | CHECK(!ast.has_errors()); 88 | 89 | AstCompare c(ast); 90 | c.function_definition(cero::AccessSpecifier::None, "bar", [](AstCompare& c) { 91 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 92 | c.name_expr("int32"); 93 | }); 94 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 95 | c.name_expr("int32"); 96 | }); 97 | c.function_parameter(cero::ParameterSpecifier::None, "c", [](AstCompare& c) { 98 | c.name_expr("int32"); 99 | }); 100 | c.function_output("", [](AstCompare& c) { 101 | c.name_expr("bool"); 102 | }); 103 | c.binding_statement(cero::BindingSpecifier::Let, "u", [](AstCompare& c) { 104 | c.binary_expr(cero::BinaryOperator::Eq, [](AstCompare& c) { 105 | c.binary_expr(cero::BinaryOperator::Sub, [](AstCompare& c) { 106 | c.name_expr("a"); 107 | c.name_expr("b"); 108 | }); 109 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 110 | c.name_expr("b"); 111 | c.name_expr("c"); 112 | }); 113 | }); 114 | }); 115 | c.binding_statement(cero::BindingSpecifier::Let, "v", [](AstCompare& c) { 116 | c.binary_expr(cero::BinaryOperator::NotEq, [](AstCompare& c) { 117 | c.binary_expr(cero::BinaryOperator::Mul, [](AstCompare& c) { 118 | c.name_expr("b"); 119 | c.name_expr("a"); 120 | }); 121 | c.binary_expr(cero::BinaryOperator::Div, [](AstCompare& c) { 122 | c.name_expr("c"); 123 | c.name_expr("a"); 124 | }); 125 | }); 126 | }); 127 | c.binding_statement(cero::BindingSpecifier::Let, "w", [](AstCompare& c) { 128 | c.binary_expr(cero::BinaryOperator::Greater, [](AstCompare& c) { 129 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 130 | c.name_expr("c"); 131 | c.name_expr("b"); 132 | }); 133 | c.binary_expr(cero::BinaryOperator::Mul, [](AstCompare& c) { 134 | c.name_expr("b"); 135 | c.name_expr("a"); 136 | }); 137 | }); 138 | }); 139 | c.binding_statement(cero::BindingSpecifier::Let, "x", [](AstCompare& c) { 140 | c.binary_expr(cero::BinaryOperator::Less, [](AstCompare& c) { 141 | c.binary_expr(cero::BinaryOperator::Div, [](AstCompare& c) { 142 | c.name_expr("b"); 143 | c.name_expr("a"); 144 | }); 145 | c.binary_expr(cero::BinaryOperator::Sub, [](AstCompare& c) { 146 | c.name_expr("c"); 147 | c.name_expr("b"); 148 | }); 149 | }); 150 | }); 151 | c.binding_statement(cero::BindingSpecifier::Let, "y", [](AstCompare& c) { 152 | c.binary_expr(cero::BinaryOperator::LessEq, [](AstCompare& c) { 153 | c.binary_expr(cero::BinaryOperator::Mul, [](AstCompare& c) { 154 | c.name_expr("a"); 155 | c.name_expr("c"); 156 | }); 157 | c.binary_expr(cero::BinaryOperator::Sub, [](AstCompare& c) { 158 | c.name_expr("b"); 159 | c.name_expr("a"); 160 | }); 161 | }); 162 | }); 163 | c.binding_statement(cero::BindingSpecifier::Let, "z", [](AstCompare& c) { 164 | c.binary_expr(cero::BinaryOperator::GreaterEq, [](AstCompare& c) { 165 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 166 | c.name_expr("b"); 167 | c.name_expr("c"); 168 | }); 169 | c.binary_expr(cero::BinaryOperator::Div, [](AstCompare& c) { 170 | c.name_expr("a"); 171 | c.name_expr("c"); 172 | }); 173 | }); 174 | }); 175 | c.return_expr([](AstCompare& c) { 176 | c.binary_expr(cero::BinaryOperator::LogicOr, [](AstCompare& c) { 177 | c.binary_expr(cero::BinaryOperator::LogicOr, [](AstCompare& c) { 178 | c.binary_expr(cero::BinaryOperator::LogicOr, [](AstCompare& c) { 179 | c.binary_expr(cero::BinaryOperator::LogicOr, [](AstCompare& c) { 180 | c.binary_expr(cero::BinaryOperator::LogicOr, [](AstCompare& c) { 181 | c.name_expr("u"); 182 | c.name_expr("v"); 183 | }); 184 | c.name_expr("w"); 185 | }); 186 | c.name_expr("x"); 187 | }); 188 | c.name_expr("y"); 189 | }); 190 | c.name_expr("z"); 191 | }); 192 | }); 193 | }); 194 | } 195 | 196 | CERO_TEST(ParseComparisonAndLogicalOperators) { 197 | auto source = make_test_source(R"_____( 198 | baz(int32 a, int32 b, int32 c, int32 d) -> bool { 199 | return a + b == b + c && b + c != c + d && a < c && a > d; 200 | } 201 | )_____"); 202 | 203 | ExhaustiveReporter r; 204 | auto ast = cero::run_parser_on_source(source, r, TabSize); 205 | CHECK(!ast.has_errors()); 206 | 207 | AstCompare c(ast); 208 | c.function_definition(cero::AccessSpecifier::None, "baz", [](AstCompare& c) { 209 | c.function_parameter(cero::ParameterSpecifier::None, "a", [](AstCompare& c) { 210 | c.name_expr("int32"); 211 | }); 212 | c.function_parameter(cero::ParameterSpecifier::None, "b", [](AstCompare& c) { 213 | c.name_expr("int32"); 214 | }); 215 | c.function_parameter(cero::ParameterSpecifier::None, "c", [](AstCompare& c) { 216 | c.name_expr("int32"); 217 | }); 218 | c.function_parameter(cero::ParameterSpecifier::None, "d", [](AstCompare& c) { 219 | c.name_expr("int32"); 220 | }); 221 | c.function_output("", [](AstCompare& c) { 222 | c.name_expr("bool"); 223 | }); 224 | c.return_expr([](AstCompare& c) { 225 | c.binary_expr(cero::BinaryOperator::LogicAnd, [](AstCompare& c) { 226 | c.binary_expr(cero::BinaryOperator::LogicAnd, [](AstCompare& c) { 227 | c.binary_expr(cero::BinaryOperator::LogicAnd, [](AstCompare& c) { 228 | c.binary_expr(cero::BinaryOperator::Eq, [](AstCompare& c) { 229 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 230 | c.name_expr("a"); 231 | c.name_expr("b"); 232 | }); 233 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 234 | c.name_expr("b"); 235 | c.name_expr("c"); 236 | }); 237 | }); 238 | c.binary_expr(cero::BinaryOperator::NotEq, [](AstCompare& c) { 239 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 240 | c.name_expr("b"); 241 | c.name_expr("c"); 242 | }); 243 | c.binary_expr(cero::BinaryOperator::Add, [](AstCompare& c) { 244 | c.name_expr("c"); 245 | c.name_expr("d"); 246 | }); 247 | }); 248 | }); 249 | c.binary_expr(cero::BinaryOperator::Less, [](AstCompare& c) { 250 | c.name_expr("a"); 251 | c.name_expr("c"); 252 | }); 253 | }); 254 | c.binary_expr(cero::BinaryOperator::Greater, [](AstCompare& c) { 255 | c.name_expr("a"); 256 | c.name_expr("d"); 257 | }); 258 | }); 259 | }); 260 | }); 261 | } 262 | 263 | } // namespace tests 264 | -------------------------------------------------------------------------------- /tests/syntax/TokenStringResults.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ExhaustiveReporter.hpp" 2 | #include "common/Test.hpp" 3 | 4 | #include 5 | 6 | namespace tests { 7 | 8 | CERO_TEST(TokenStringForBracketsLiterals) { 9 | auto source = make_test_source(R"_____( 10 | () [] {} <> 11 | foo "bar" 'baz' 123 456; 12 | 0x12345 456 aff; 13 | 0b011 01 101; 14 | 0o03423362 63; 15 | 1.345634634 234623 16 | )_____"); 17 | 18 | ExhaustiveReporter r; 19 | auto tokens = cero::run_lexer(source, r, true, TabSize); 20 | CHECK(!tokens.has_errors()); 21 | auto str = tokens.to_string(source, TabSize); 22 | 23 | CHECK_EQ(str, R"_____(Token stream for TokenStringForBracketsLiterals (21 tokens) 24 | LParen `(` [2:1] 25 | RParen `)` [2:2] 26 | LBracket `[` [2:4] 27 | RBracket `]` [2:5] 28 | LBrace `{` [2:7] 29 | RBrace `}` [2:8] 30 | LAngle `<` [2:10] 31 | RAngle `>` [2:11] 32 | Name `foo` [3:1] 33 | StringLiteral `"bar"` [3:5] 34 | CharLiteral `'baz'` [3:11] 35 | DecIntLiteral `123 456` [3:17] 36 | Semicolon `;` [3:24] 37 | HexIntLiteral `0x12345 456 aff` [4:1] 38 | Semicolon `;` [4:16] 39 | BinIntLiteral `0b011 01 101` [5:1] 40 | Semicolon `;` [5:14] 41 | OctIntLiteral `0o03423362 63` [6:1] 42 | Semicolon `;` [6:14] 43 | FloatLiteral `1.345634634 234623` [7:1] 44 | EndOfFile `` [8:1] 45 | )_____"); 46 | } 47 | 48 | CERO_TEST(TokenStringForOperators) { 49 | auto source = make_test_source(R"_____( 50 | + - * / % & | ~ << >> 51 | && || == != 52 | )_____"); 53 | 54 | ExhaustiveReporter r; 55 | auto tokens = cero::run_lexer(source, r, true, TabSize); 56 | CHECK(!tokens.has_errors()); 57 | auto str = tokens.to_string(source, TabSize); 58 | 59 | CHECK_EQ(str, R"_____(Token stream for TokenStringForOperators (16 tokens) 60 | Plus `+` [2:1] 61 | Minus `-` [2:3] 62 | Star `*` [2:5] 63 | Slash `/` [2:7] 64 | Percent `%` [2:9] 65 | Amp `&` [2:11] 66 | Pipe `|` [2:13] 67 | Tilde `~` [2:15] 68 | LAngleLAngle `<<` [2:17] 69 | RAngle `>` [2:20] 70 | RAngle `>` [2:21] 71 | AmpAmp `&&` [3:1] 72 | PipePipe `||` [3:4] 73 | EqEq `==` [3:7] 74 | BangEq `!=` [3:10] 75 | EndOfFile `` [4:1] 76 | )_____"); 77 | } 78 | 79 | CERO_TEST(TokenStringForKeywords) { 80 | auto source = make_test_source(R"_____( 81 | struct S { 82 | enum E { 83 | } 84 | 85 | f() -> int32 { 86 | return 0; 87 | } 88 | } 89 | )_____"); 90 | 91 | ExhaustiveReporter r; 92 | auto tokens = cero::run_lexer(source, r, true, TabSize); 93 | CHECK(!tokens.has_errors()); 94 | auto str = tokens.to_string(source, TabSize); 95 | 96 | CHECK_EQ(str, R"_____(Token stream for TokenStringForKeywords (19 tokens) 97 | Struct `struct` [2:1] 98 | Name `S` [2:8] 99 | LBrace `{` [2:10] 100 | Enum `enum` [3:5] 101 | Name `E` [3:10] 102 | LBrace `{` [3:12] 103 | RBrace `}` [4:5] 104 | Name `f` [6:5] 105 | LParen `(` [6:6] 106 | RParen `)` [6:7] 107 | ThinArrow `->` [6:9] 108 | Name `int32` [6:12] 109 | LBrace `{` [6:18] 110 | Return `return` [7:9] 111 | DecIntLiteral `0` [7:16] 112 | Semicolon `;` [7:17] 113 | RBrace `}` [8:5] 114 | RBrace `}` [9:1] 115 | EndOfFile `` [10:1] 116 | )_____"); 117 | } 118 | 119 | } // namespace tests 120 | -------------------------------------------------------------------------------- /vcpkg-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "default-registry": { 3 | "kind": "git", 4 | "baseline": "e9c53cd6c198a5c16c2e249ae67f5a73aab84b17", 5 | "repository": "https://github.com/microsoft/vcpkg" 6 | }, 7 | "registries": [ 8 | { 9 | "kind": "artifact", 10 | "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", 11 | "name": "microsoft" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cero", 3 | "version": "0.0.1", 4 | "dependencies": [ 5 | "cxxopts", 6 | "fmt", 7 | "pkgconf", 8 | "gmp", 9 | "doctest" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------