diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9b23e67 --- /dev/null +++ b/.clang-format @@ -0,0 +1,87 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + AfterObjCDeclaration: false + IndentBraces: false +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ContinuationIndentWidth: 4 +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: ([-_](test|unittest))?$'([-_](test|unittest))?$' +IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PointerAlignment: Left +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..e46b6ed --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,20 @@ +Checks: '-*, + modernize-*, + performance-*, + cppcoreguidelines-*, + bugprone-*, + -modernize-use-trailing-return-type, + -performance-no-int-to-ptr, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -bugprone-exception-escape, + -bugprone-easily-swappable-parameters' + +WarningsAsErrors: '' +HeaderFilterRegex: '' +FormatStyle: none +CheckOptions: + - key: modernize-use-using.IgnoreMacros + value: 'true' diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index a670a69..b46f230 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -18,23 +18,23 @@ jobs: - name: Build main target shell: bash run: | - cmake --build cmake-build-release --target cpp_tests || echo Built with errors + cmake --build cmake-build-release || echo Built with errors - name: Build tests target shell: bash run: | - cmake --build cmake-build-debug --target cpp_tests_tests || echo Built with errors + cmake --build cmake-build-debug --target ovum_tests || echo Built with errors - name: Run program working-directory: .\cmake-build-release run: | - .\cpp_tests.exe --help + .\ovum.exe --help - name: Run tests working-directory: .\cmake-build-debug run: | echo "Currently unable to run tests on Windows Latest MinGW. See https://gitmemories.com/cristianadam/HelloWorld/issues/12 and https://github.com/microsoft/vscode-cmake-tools/issues/2451" - % .\cpp_tests_tests.exe + % .\ovum_tests.exe build-matrix: name: Tests and application run on ${{ matrix.config.name }} @@ -72,22 +72,22 @@ jobs: - name: Build main target shell: bash run: | - cmake --build cmake-build-release --target cpp_tests || echo "Built with errors" + cmake --build cmake-build-release --target ovum || echo "Built with errors" - name: Build tests target shell: bash run: | - cmake --build cmake-build-debug --target cpp_tests_tests || echo "Built with errors" + cmake --build cmake-build-debug --target ovum_tests || echo "Built with errors" - name: Run program shell: bash working-directory: ./cmake-build-release run: | if [ "$RUNNER_OS" == "Windows" ]; then - ./cpp_tests.exe --help + ./ovum.exe --help else cd bin - ./cpp_tests --help + ./ovum --help fi - name: Run tests @@ -95,10 +95,10 @@ jobs: working-directory: ./cmake-build-debug run: | if [ "$RUNNER_OS" == "Windows" ]; then - ./cpp_tests_tests.exe + ./ovum_tests.exe else cd tests - ./cpp_tests_tests + ./ovum_tests fi memory-leaks: @@ -117,9 +117,9 @@ jobs: - name: Build tests target run: | - cmake --build cmake-build --target cpp_tests_tests + cmake --build cmake-build --target ovum_tests - name: Run valgrind working-directory: ./cmake-build/tests run: | - valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./cpp_tests_tests + valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./ovum_tests diff --git a/.gitignore b/.gitignore index 50c98fc..ce2ca85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # CMake **cmake-build*/ +build/ # All idea files **.idea*/ @@ -10,6 +11,15 @@ # IntelliJ **out/ +# Visual Studio Code +**.vscode/ +**.history/ + +# Clangd +compile_commands.json +compile_flags.txt +.clangd + # mpeltonen/sbt-idea plugin **.idea_modules/ @@ -22,6 +32,18 @@ crashlytics.properties crashlytics-build.properties fabric.properties +# Notepad++ temporary file +*.bak + +# API keys +*.apikey + +# Cache directories +**cache*/ + +# Program settings files +**.config*/compiler/* + # Prerequisites *.d diff --git a/CMakeLists.txt b/CMakeLists.txt index c03953b..a807f1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,30 +1,14 @@ cmake_minimum_required(VERSION 3.12) project( - cpp_tests # Rename the project here and in ci_tests.yml - VERSION 0.1 - DESCRIPTION "C++ Project with Google tests" - LANGUAGES CXX + ovum + VERSION 0.1 + DESCRIPTION "Ovum programming language toolset" + LANGUAGES CXX ) -set(CMAKE_CXX_STANDARD 20) - -if (WIN32) # Install dlls in the same directory as the executable on Windows - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) -endif () - -if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CMAKE_CXX_FLAGS_DEBUG "/MDd") - set(CMAKE_CXX_FLAGS_RELEASE "/O2") -else () - set(CMAKE_CXX_FLAGS_DEBUG "-g") - set(CMAKE_CXX_FLAGS_RELEASE "-O3") -endif () +include(cmake/SetCompilerOptions.cmake) +include(cmake/IncludeExternalLibraries.cmake) add_subdirectory(lib) add_subdirectory(bin) diff --git a/CODEBASE_STRUCTURE.md b/CODEBASE_STRUCTURE.md new file mode 100644 index 0000000..7a22c8c --- /dev/null +++ b/CODEBASE_STRUCTURE.md @@ -0,0 +1,32 @@ +# Base Repository Structure + +## Directory Purposes + +### Root Directory +- **Project documentation** — README, build instructions, developer guides +- **Build configuration** — `CMakeLists.txt`, auxiliary CMake scripts +- **Tooling settings** — configurations for formatting, linters, Git +- **Automation scripts** — dependency installation, environment setup + +### `bin/` +- **Executables** — application entry point +- **CLI interface** — command line for user interaction + +### `docs/` +- **Project documentation** — README, build instructions, developer guides + +### `lib/` +- **Core library** — the project's core functionality +- **UI module** — user interface and CLI utilities +- **Shared components** — reusable code parts. Add new modules only under this directory. + +### `tests/` +- **Unit tests** — testing individual components +- **Integration tests** — testing interactions between modules +- **Helper functions** — utilities for testing +- **Test data** — examples and fixtures for tests + +### `.github/workflows/` +- **CI/CD configuration** — automated build and testing +- **Code quality checks** — static analysis, formatting +- **Deployment** — automated release publishing diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..359586c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [bigulov.sasha@gmail.com](mailto:bigulov.sasha@gmail.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md new file mode 100644 index 0000000..1b5dae5 --- /dev/null +++ b/CODING_GUIDELINES.md @@ -0,0 +1,342 @@ +# Style guide + +This document collects all examples of correct code formatting for our project. + +Primarily follow the Google Code Style: https://google.github.io/styleguide/cppguide.html. +If there is any conflict, follow this document. +There is a ClangFormat file (.clang-format) you can use for automatic formatting. + +### 1. Maximum line length — 80 characters. + +### 2. Use spaces, not tabs. Configure your editor for 2 spaces. + +### 3. Use snake_case for variable names, and PascalCase for function names: + +```c++ +int32_t MakingSomeStuff() { + ... +} + +int main() { + int32_t some_value = 144; + std::string person_name = "Alice"; + std::string base = "we_will_rock_you"; +} +``` + +BUT! When it comes to class/enum/struct/etc names, use PascalCase. + +At the same time, when creating an object of a class, use snake_case. +If it’s a private or protected field, append a trailing underscore. +Constants use PascalCase with a `k` prefix. + +```c++ +class SomeClass { +public: + SomeClass(); + void DoSomeStuff(); + int32_t some_value; + static const int32_t kSomeFixedValue = 10; +private: + int32_t some_value_; +}; + +enum Role { + kOfficer = 0, + kPostman, + kWaiter, + kBooster +} + +struct UsersData { + std::string name[3]; + uint32_t age; + Role role; +} +``` + +### 4. Use fixed-width types. For example, `int32_t` instead of `int`, `uint64_t` instead of `unsigned long`. + +### 5. Allow `auto` only: +- When the type is explicit (e.g., via an explicit constructor call or otherwise clearly specified) +- For tuple unpacking + +IN ALL OTHER CASES — AVOID + +### 6. Bracing style: +When declaring new classes/functions/structs/enums/lambdas/etc, put the declaration and opening brace on the same line: + +```c++ +struct TypicalExperimentData { + short num_of_experiment; + int32_t weights[10]; + float temperatures[5]; +}; + +void DoSomeGreatStuff() { + if (true) { + std::cout << "Hello, world!"; + } else { + std::cout << "What a wonderful world!"; + } +} + +int main() { + return 0; +} +``` + +### 7. Blank lines: + +- After each function, class, struct, enum, lambda, etc. +- Before and after each code block inside a function — anything delimited by braces, e.g., `for` or `if`. +- Between variables if they’re unrelated. +- Between `#include` and the code. +- Before `return` in a function. +- Before access specifiers in a class, except the first one. + +GodObject.hpp +```c++ +class GodObject { // Note the order of access specifiers +public: + GodObject(); + ~GodObject(); + int32_t GetSomeValue(); + int32_t CalculateSomething(); + int32_t field; + +protected: + int32_t some_value_; + +private: + int32_t another_value_; +} +``` + +GodObject.cpp +```c++ +#include "GodObject.hpp" + +GodObject::GodObject() : some_value_(0), another_value_(0) {} + +GodObject::~GodObject() {} + +int32_t GodObject::GetSomeValue() { + return some_value_; +} + +int32_t GodObject::CalculateSomething() { + int32_t sum = 0; + + for (int32_t i = 0; i < 10; ++i) { + sum += i; + } + + return sum * another_value_; +} +``` + +### 8. Naming: + +- Functions are named by what they do — for example, if a function “finds” something: + ```c++ + float FindMatrixDeterminant + ``` + +In other words, use an appropriate verb. + +For example, if we’re making a game — say, Snake. We have the user and the score. +In this case we can describe the user with a class and create at least two public functions: + +```c++ +float Player::CalculateScore() { + ... +} +``` + +Names should be, as much as possible, **clear**, **short**, and **intuitive** for anyone working on the project. +Maintain a balance between clarity and length: avoid names longer than 20 characters and 4 words. +Use abbreviations only if they are widely accepted (for example, a data format name), and when used, write them in lowercase. + +- Name variables **meaningfully**: + +```c++ +int a = 12; // BAD! + +int drops_count = 12; // More or less clear what this is +``` + +No i, j, k, a, fizz, buzz, etc. + +Do not use global variables. +If a variable is used in multiple functions, it must be passed as an argument. + + +- Name classes using nouns. For example: + +```c++ +class Engine { + ... +} + +class TableInfo{ + ... +} + +class AbstactMixer { + ... +} +``` + +- Name structs the same way as classes: + +```c++ +struct List { + int8_t item; + float* pf; + List* next; +}; + +struct TypicalExperimentData { + int16_t num_of_experiment; + int32_t weights[10]; + float temperatures[3]; +}; +``` + +- Name concepts with adjectives: + +```c++ +template +concept Numeric = std::is_arithmetic_v; +``` + +### 9. Separate the function declaration and its implementation between `.hpp` and `.cpp`, respectively +For example: + +main.cpp +```c++ +#include "my_func.hpp" + +int main() { + Square(10); + return 0; +} +``` + +- Use include guards! + +my_func.hpp +```c++ +#ifndef MYFUNC_HPP +#define MYFUNC_HPP + +void Square(int32_t); + +#endif // MYFUNC_HPP +``` + +- About `include`: + - Include external libraries via `#include <...>`, and our own files via `#include "..."`. + - Put all `include`s at the top of the file. + - Write all `include`s in alphabetical order. + - Write all `include`s in the following sequence, with each category separated by a blank line: + - First the standard library + - Then external libraries + - Then other libraries of ours + - Then header files of the current library + +my_func.cpp +```c++ +#include + +#include + +#include + +#include "my_func.hpp" + +int Square(int32_t a) { + return a * a; +} +``` + +### 10. Source code organization. + +Use classes to store data associated with specific objects. +A class describes the object model, fields that store data, and methods to interact with the object. +First describe the class structure in the `.hpp` file, and then describe method implementations in the `.cpp` file. +There are no exceptions — constructors count too. +Use structs only as DTOs (Data Transfer Objects). + +> Unlike Google Code Style, we name header files the same as the classes they describe. +> Files that contain class method implementations are named the same as the headers but with a `.cpp` extension. +> Files that contain only functions are named in snake_case by general purpose. + +Rule: one class — one header file. + +IdealGas.hpp +```c++ +class IdealGas { +public: + IdealGas(double t = 0.0, double n = 0.0, double v = 0.0, double p = 0.0); + double GetP(); + double GetT(); + double GetV(); + double GetN(); + double CalculateR(); + +private: + double t_; + double n_; + double p_; + double v_; +}; +``` + +IdealGas.cpp +```c++ +#include "IdealGas.hpp" + +IdealGas::IdealGas(double t, double n, double v, double p) : t_(t), n_(n), v_(v), p_(p) {} + +double IdealGas::GetP() { + return p_; +} + +// Implementation of the remaining class methods +``` + +When creating class instances, use smart pointers where possible (to avoid memory leaks): + +```c++ +#include + +int main() { + std::unique_ptr ideal_gas; + ideal_gas = std::make_unique(10.0, 10.0, 10.0, 10.0); + // Use ideal_gas + ideal_gas = std::make_unique(20.0, 20.0, 20.0, 20.0); + + return 0; +} +``` + +> Aim to use modern C++; for example, almost always prefer `std::array` over `char[N]`. + +#### On lambda functions + +Use lambda functions when needed, but don’t overuse them. +For example, when you need to capture some context, or to create a very short function to pass somewhere. + +### 11. Data passing organization. + +To pass data to functions, use references and pointers to instances of the classes that describe the objects you interact with: +- Correct: +```c++ +void SetIdealGas(std::unique_ptr ideal_gas); +``` +- INCORRECT +```c++ +void SetIdealGase(double P, double V, double N, double T, double R, double x, double y, double z, double n, double m, double t); +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ab40fc1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing Guide + +- Contributing to The Ovum Language is fairly easy. This document shows you how to get started + +## General +- The [Codebase Structure](./CODEBASE_STRUCTURE.md) has detailed information about how the various files in this project are structured +- Please ensure that any changes you make are in accordance with the [Coding Guidelines](./CODING_GUIDELINES.md) of this repo + +## Submitting changes + +- Fork the repo (if you're not the maintainer) + - +- Check out a new branch based and name it to what you intend to do: + - Example: + ```` + $ git checkout -b BRANCH_NAME + ```` + If you get an error, you may need to fetch fooBar first by using + ```` + $ git remote update && git fetch + ```` + - Use one branch per fix / feature +- Commit your changes + - Please provide a git message that explains what you've done + - Please make sure your commits follow the [Semantic Commit Messages](https://seesparkbox.com/foundry/semantic_commit_messages) + - Commit to the forked repository + - Example: + ```` + $ git commit -am 'Add some fooBar' + ```` +- Push to the branch + - Example: + ```` + $ git push origin BRANCH_NAME + ```` +- Make a pull request + - Make sure you send the PR to the fooBar branch + - CI is watching you! + +If you follow these instructions, your PR will land pretty safely in the main repo! diff --git a/README.md b/README.md index 465c79d..7521dbe 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,226 @@ -# C++ project template with Google Tests and CI/CD +# Ovum Programming Language -This is a project template. Feel free to use & fork it. It contains all pre-configured -CMakeLists.txt, so to use it, replace project name with your one in -[main CmakeLists.txt](CMakeLists.txt), and all target and executable names in -[CI/CD script](./.github/workflows/ci_tests.yml). Sample program prints a greeting for the first argument. +Ovum is a strongly statically typed, single-threaded language focused on safety, clarity, and performance. It uses Kotlin-like syntax, immutability by default, a GC + JIT VM, interface-based polymorphism, and support for pure functions and functional objects. -## How to build and run +- Contribute: see [`CONTRIBUTING.md`](CONTRIBUTING.md). +- Project docs: [`docs/README.md`](docs/README.md). +- Full language reference: [`docs/reference/README.md`](docs/reference/README.md). -Run the following commands from the project directory. +--- -1. Create CMake cache +## [Overview](docs/reference/design.md) -```shell -cmake -S . -B cmake-build -``` +- Memory safety via GC; no manual memory management. +- Immutable by default; explicit mutation with `var`. +- Pure functions (`pure`) are side-effect free and may be cached on VM level. +- Interface-based polymorphism; no class inheritance. +- Explicit `unsafe { ... }` for low-level operations. +- Minimal, predictable syntax; no user-defined operators. +- No generics/templates, only ad-hoc and subclass polymorphism. -2. Build executable target +--- -```shell -cmake --build cmake-build --target cpp_tests -``` +## [Syntax at a Glance](docs/reference/syntax.md) -3. Build tests target +- Files: `.ovum`. Names: PascalCase for types/methods. +- Functions: `fun Name(a: T): U { ... }`; pure: `pure fun ...`. +- Classes: `class Name implements IFace { ... }`; interfaces: `interface IFace { ... }`. +- Fields: `val` (immutable) or `var` (mutable); access modifiers are mandatory. +- Namespaces: `::` (e.g., `sys::Print`). Basic preprocessor: `#import`, `#define`, `#ifdef`… -```shell -cmake --build cmake-build --target cpp_tests_tests -``` +--- -4. Run executable target +## [Types and Nullability](docs/reference/types.md) -* On Windows: +- Nullable types: append `?` (e.g., `Int?`). +- Safe call `?.`, Elvis `?:`, non‑null `!!`. +- Type test `is`, cast `as` (downcast yields nullable type). +- Explicit cast to `Bool` allowed for any value. -```shell -.\cmake-build\cpp_tests.exe World -``` +--- + +## [Control Flow](docs/reference/control_flow.md) + +- `if/else`, `while`, `for (x in xs)`. +- `return`, `break`, `continue`; no `goto`. + +--- + +## [Expressions and Operators](docs/reference/expressions_and_operators.md) + +- Arithmetic: `+ - * / %` +- Comparison: `== != < <= > >=` +- Boolean: `&& || ! xor` (short‑circuit `&&`/`||`). +- Assignment: `=` +- Member/calls: `. ()` and safe `?.` +- Null handling: `?. ?: !!` +- Type ops: `as`, `is` +- Namespace: `::` + +--- + +## [Object Model](docs/reference/object_model.md) + +- Classes implement interfaces; no class inheritance. +- Methods declare access; support `override` and `pure`. +- Optional destructor; called by GC (manual calls are unsafe). + +### Functional Objects (`call`) + +- Classes or interfaces can define a special `call` member to be callable. +- Function literals can coerce to an interface exposing a compatible `call`. + +--- + +## [Runtime and Tooling](docs/reference/runtime.md) + +- Pipeline: `.ovum` → bytecode → Ovum VM. +- GC for memory safety; JIT compiles hot paths. +- Single‑threaded execution model. +- Architectures: amd64, arm64. Numeric widths: `Int` 8 bytes, `Float` 8 bytes. +- Entry point: `Main(args: StringArray): Int`. -* On *nix: +Build & Run (conceptual): write `.ovum`, compile (parse, type‑check, enforce const/pure), run on VM (JIT + GC). -```shell -./cmake-build/bin/cpp_tests World +--- + +## [System Library and Interop](docs/reference/system_library.md) + +- `sys::Print(msg: String): Void` +- `sys::Time(): Int` +- `sys::Sleep(ms: Int): Void` +- `sys::Exit(code: Int): Never` +- FFI: `sys::Interope(dllName, functionName, input: ByteArray, output: ByteArray): Int` (unsafe) + +--- + +## [Unsafe (recap)](docs/reference/unsafe.md) + +Only inside `unsafe { ... }`: +- Global `var` and `static var` writes. +- Const/mutable casts; `Pointer`, address‑of, dereference. +- Manual destructor calls. +- `sys::Interope`; casting any value to (const or mutable) `ByteArray`. + +--- + +## [Code Examples](docs/reference/code_examples.md) + +### Entry point (`StringArray`) + +```ovum +// .ovum file +fun Main(args: StringArray): Int { + val count: Int = args.Length() + sys::Print("Args count: " + count.ToString()) + return 0 +} ``` -5. Run tests +### Pure functions with caching -* On Windows: +```ovum +pure fun Fib(n: Int): Int { + if (n <= 1) return n + return Fib(n - 1) + Fib(n - 2) +} +``` -```shell -.\cmake-build\cpp_tests_tests.exe +### `is`, `as`, `!!` and ByteArray casts + +```ovum +fun DemoCasts(obj: Object): Void { + if (obj is Point) { + val p: Point = (obj as Point)!! // nullable cast + assert + sys::Print(p.ToString()) + } + + // Bool cast + val b1: Bool = (0 as Bool) // false + val b2: Bool = (42 as Bool) // true + val b3: Bool = (obj as Bool) // always true + val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + + // Unsafe: raw byte views + unsafe { + val bytesConst: ByteArray = (obj as ByteArray) + val bytesMut : ByteArray = (obj as var ByteArray) + } +} ``` -* On *nix: +### Functional objects (`call`) & literals + +```ovum +interface CustomFunctional { + call(a: Int?, b: Int?): Int +} + +class DefinedFunctional { + public var Multiplier: Int + + public fun DefinedFunctional(multiplier: Int): DefinedFunctional { + this.Multiplier = multiplier + return this + } + + public call(secondMultiplier: Int): Int = fun(secondMultiplier: Int): Int { + return Multiplier * secondMultiplier + } +} + +val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { + return (a ?: 0) + (b ?: 0) +} + +fun Main(args: StringArray): Int { + return AddNullable(2, DefinedFunctional(-1)(2)) +} +``` -```shell -./cmake-build/tests/cpp_tests_tests +### Complete program example + +```ovum +// Complete Ovum program demonstrating key features +interface ICalculator { + fun Calculate(a: Int, b: Int): Int +} + +class Adder implements ICalculator { + public override fun Calculate(a: Int, b: Int): Int { + return a + b + } +} + +class Multiplier implements ICalculator { + public override fun Calculate(a: Int, b: Int): Int { + return a * b + } +} + +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { + var result: Int = 0 + for (num in numbers) { + result = result + calc.Calculate(num, 2) + } + return result +} + +fun Main(args: StringArray): Int { + val numbers: IntArray = IntArray(3) + numbers[0] = 5 + numbers[1] = 10 + numbers[2] = 15 + + val adder: ICalculator = Adder() + val multiplier: ICalculator = Multiplier() + + val sumResult: Int = ProcessNumbers(adder, numbers) + val productResult: Int = ProcessNumbers(multiplier, numbers) + + sys::Print("Sum result: " + sumResult.ToString()) + sys::Print("Product result: " + productResult.ToString()) + + return 0 +} ``` diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index c84570b..e9bb8f8 100644 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -1,13 +1,11 @@ -add_executable(${PROJECT_NAME} main.cpp) - -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) # Main executable should be built with Release -endif () +add_executable(${PROJECT_NAME} + main.cpp +) message(STATUS "Main executable build type: ${CMAKE_BUILD_TYPE}") target_link_libraries(${PROJECT_NAME} PUBLIC - ui + ui ) -target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}) \ No newline at end of file +target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}) diff --git a/cmake/IncludeExternalLibraries.cmake b/cmake/IncludeExternalLibraries.cmake new file mode 100644 index 0000000..d002ab7 --- /dev/null +++ b/cmake/IncludeExternalLibraries.cmake @@ -0,0 +1,13 @@ +include(FetchContent) + +# GoogleTest +# For Windows: prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) + +FetchContent_MakeAvailable(googletest) diff --git a/cmake/SetCompilerOptions.cmake b/cmake/SetCompilerOptions.cmake new file mode 100644 index 0000000..86de13c --- /dev/null +++ b/cmake/SetCompilerOptions.cmake @@ -0,0 +1,25 @@ +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS_DEBUG "/MDd") + set(CMAKE_CXX_FLAGS_RELEASE "/O2") +else() + set(CMAKE_CXX_FLAGS_DEBUG "-g") + set(CMAKE_CXX_FLAGS_RELEASE "-O3") +endif() + +# Windows: place runtime artifacts next to the executable +if(WIN32) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) +endif() diff --git a/docs/BUILD.md b/docs/BUILD.md new file mode 100644 index 0000000..37e38cd --- /dev/null +++ b/docs/BUILD.md @@ -0,0 +1,58 @@ +# Build instructions for Ovum toolset + +This documents describes build process for Ovum programming language toolset and tests. + +## How to build and run + +Run the following commands from the project directory. + +1. Create CMake cache + +```shell +cmake -S . -B cmake-build +``` + +2. Build all targets + +```shell +cmake --build cmake-build +``` + +4. Run main executable + +* On Windows: + +```shell +.\cmake-build\ovum.exe +``` + +* On *nix: + +```shell +./cmake-build/bin/ovum World +``` + +5. Run tests + +* On Windows: + +```shell +.\cmake-build\ovum_tests.exe +``` + +* On *nix: + +```shell +./cmake-build/tests/ovum_tests +``` + +6. Installation + +Running this script will build and install Ovum main executable at `$HOME/ovum`. +Define `$SAVE_PREV` environment variable to save previous configuration. + +> There is only a shell script, so on Windows one should use Git Bash. + +```shell +./install.sh +``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..508f41d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Ovum Documentation Page + +This page contains documentation for the Ovum programming language. + +## Build instructions + +If you want to build Ovum from scratch, please read the [BUILD.md](./BUILD.md) file. + +## Ovum Language Reference + +If you want to read the Ovum language reference, please read the [README.md](../README.md) file. diff --git a/docs/reference/README.md b/docs/reference/README.md new file mode 100644 index 0000000..1f92c91 --- /dev/null +++ b/docs/reference/README.md @@ -0,0 +1,28 @@ +# Ovum Language Reference + +This page contains the Ovum language reference. + +## About Ovum + +* [Ovum design principles](./design.md) +* [Ovum runtime](./runtime.md) +* [Code examples](./code_examples.md) + +## Language rules + +* [Lexical structure](./lexical_structure.md) +* [Syntax and semantics](./syntax.md) +* [Expressions and operators](./expressions_and_operators.md) +* [Type system](./types.md) +* [Control flow](./control_flow.md) +* [Unsafe operations](./unsafe.md) + +## Type system + +* [Object model](./object_model.md) +* [Nullable types](./nullable.md) + +## Standard functionality + +* [Built-in types](./builtin_types.md) +* [Standard library](./system_library.md) diff --git a/docs/reference/builtin_types.md b/docs/reference/builtin_types.md new file mode 100644 index 0000000..6565b19 --- /dev/null +++ b/docs/reference/builtin_types.md @@ -0,0 +1,225 @@ +# Built-in Reference Types + +This document describes the built-in reference types in Ovum, their methods, and the standard interfaces that all types implement. + +## String Type + +`String` is a reference type (not primitive) for text data. All strings are immutable by default and implement `IStringConvertible`, `IComparable`, and `IHashable`. + +### String Methods + +* `Length(): Int` - Returns the number of characters in the string +* `ToString(): String` - Returns the string itself (implements `IStringConvertible`) +* `IsLess(other: Object): Bool` - Lexicographic comparison (implements `IComparable`) +* `GetHash(): Int` - Returns hash code for the string (implements `IHashable`) +* `+` operator - String concatenation (e.g., `"Hello" + " " + "World"`) + +### String Usage + +```ovum +val greeting: String = "Hello, World!" +val length: Int = greeting.Length() // Returns 13 +val combined: String = "Hello" + ", " + "World!" + +// String comparison +val a: String = "apple" +val b: String = "banana" +val isLess: Bool = a.IsLess(b) // true (lexicographic order) + +// String hashing +val hash: Int = greeting.GetHash() +``` + +## Nullable Types + +> **Note**: For detailed information about nullable types, see [`nullable.md`](nullable.md). This section only covers basic information. + +Any primitive type can be made nullable by appending `?` (e.g., `Int?`, `String?`). Nullable types are passed by reference and can hold either a value or `null`. + +**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.` or non-null assertion `!!`. + +```ovum +val nullableString: String? = "Hello" +// val length: Int = nullableString.Length() // ERROR: Cannot call method directly on nullable +val safeLength: Int = nullableString?.Length() ?: 0 // Correct: Use safe call +val forcedLength: Int = nullableString!!.Length() // Correct: Use non-null assertion +``` + +## Array Types + +Ovum provides specialized array classes for different data types. Arrays are reference types and support indexing, iteration, and length operations. All array types implement `IStringConvertible`, `IComparable`, and `IHashable`. + +### Primitive Arrays + +* `IntArray` - Array of 64-bit signed integers +* `FloatArray` - Array of 64-bit floating-point numbers +* `BoolArray` - Array of Boolean values +* `CharArray` - Array of characters +* `ByteArray` - Array of 8-bit unsigned integers +* `PointerArray` - Array of raw memory addresses (unsafe) + +### Object Arrays + +* `ObjectArray` - Array of any object type (implements `Object`) +* `StringArray` - Convenience array of `String` objects (used for `Main` function) + +## File Type + +`File` is a reference type for file operations. Files are nullable by default (`File?`) since file operations can fail. The `File` type implements `IStringConvertible`, `IComparable`, and `IHashable`. + +### File Methods + +* `ReadAllBytes(): ByteArray?` - Reads all bytes from the file, returns `null` on error +* `ReadAllText(): String?` - Reads all text from the file as UTF-8, returns `null` on error +* `WriteAllBytes(data: ByteArray): Bool` - Writes all bytes to the file, returns `false` on error +* `WriteAllText(text: String): Bool` - Writes all text to the file as UTF-8, returns `false` on error +* `AppendText(text: String): Bool` - Appends text to the file, returns `false` on error +* `Close(): Void` - Closes the file handle +* `IsOpen(): Bool` - Returns `true` if the file is currently open +* `GetSize(): Int?` - Returns file size in bytes, or `null` if error +* `ToString(): String` - Returns file path (implements `IStringConvertible`) +* `IsLess(other: Object): Bool` - Compares file paths lexicographically (implements `IComparable`) +* `GetHash(): Int` - Returns hash of file path (implements `IHashable`) + +### File Usage + +```ovum +// File operations +val file: File? = sys::OpenFile("data.txt", sys::FileMode::Read) +if (file != null) { + val content: String? = file.ReadAllText() + if (content != null) { + sys::Print("File content: " + content) + } + file.Close() +} + +// Writing to file +val outputFile: File? = sys::OpenFile("output.txt", sys::FileMode::Write) +if (outputFile != null) { + val success: Bool = outputFile.WriteAllText("Hello, World!") + if (success) { + sys::Print("File written successfully") + } + outputFile.Close() +} + +// File comparison +val file1: File? = sys::OpenFile("a.txt", sys::FileMode::Read) +val file2: File? = sys::OpenFile("b.txt", sys::FileMode::Read) +if (file1 != null && file2 != null) { + val isLess: Bool = file1.IsLess(file2) // Compares file paths +} +``` + +### Array Methods + +All array types support the following methods: + +* `Length(): Int` - Returns the number of elements in the array +* `[index]: ElementType` - Indexing operator for element access +* `[index] = value` - Assignment operator for mutable arrays +* `ToString(): String` - String representation of the array (implements `IStringConvertible`) +* `IsLess(other: Object): Bool` - Lexicographic comparison of array elements (implements `IComparable`) +* `GetHash(): Int` - Hash code based on array contents (implements `IHashable`) + +### Array Usage + +```ovum +// Creating and using arrays +val numbers: IntArray = IntArray(3) +numbers[0] = 10 +numbers[1] = 20 +numbers[2] = 30 + +val count: Int = numbers.Length() // Returns 3 + +// Iteration +for (num in numbers) { + sys::Print(num.ToString()) +} + +// Array comparison +val arr1: IntArray = IntArray(2) +arr1[0] = 1 +arr1[1] = 2 + +val arr2: IntArray = IntArray(2) +arr2[0] = 1 +arr2[1] = 3 + +val isLess: Bool = arr1.IsLess(arr2) // true (lexicographic comparison) + +// Array hashing +val hash: Int = numbers.GetHash() + +// String array (used in Main) +fun Main(args: StringArray): Int { + for (arg in args) { + sys::Print("Argument: " + arg) + } + return 0 +} +``` + +## Built-in Interfaces + +All types in Ovum implicitly implement certain standard interfaces that provide common functionality. + +### Object (Root Interface) + +The implicit root interface for all types. Provides: +* `destructor(): Void` - Virtual destructor called by GC during finalization + +### IStringConvertible + +Provides string conversion capability: +* `ToString(): String` - Converts the object to its string representation + +All built-in types implement this interface: +```ovum +val num: Int = 42 +val str: String = num.ToString() // "42" + +val flag: Bool = true +val flagStr: String = flag.ToString() // "true" +``` + +### IComparable + +Provides ordering capability for sorting and comparison: +* `IsLess(other: Object): Bool` - Returns true if this object is less than the other + +Required for user-defined types used as parameters to pure functions (ensures stable ordering). + +```ovum +val a: Int = 5 +val b: Int = 10 +val isLess: Bool = a.IsLess(b) // true +``` + +### IHashable + +Provides hashing capability for use in hash tables and caching: +* `GetHash(): Int` - Returns a hash code for the object + +```ovum +val text: String = "Hello" +val hash: Int = text.GetHash() +``` + +## Type Hierarchy + +``` +Object (implicit root) +├── IStringConvertible +├── IComparable +└── IHashable + +Built-in Types: +├── String (implements all interfaces) +├── File (implements all interfaces) +├── IntArray, FloatArray, etc. (implements all interfaces) +├── Int, Float, Bool, Char, Byte (implements all interfaces) +└── Int?, String?, File?, etc. (nullable versions, implements all interfaces) +``` diff --git a/docs/reference/code_examples.md b/docs/reference/code_examples.md new file mode 100644 index 0000000..711160e --- /dev/null +++ b/docs/reference/code_examples.md @@ -0,0 +1,324 @@ +# Code Examples + +Here are some code examples to help you get started with Ovum. + +## 1) Entry point (`StringArray`) + +```ovum +// .ovum file +fun Main(args: StringArray): Int { + val count: Int = args.Length() + sys::Print("Args count: " + count.ToString()) + return 0 +} +``` + +## 2) Variables, Nulls, Elvis, Safe Calls + +```ovum +fun DemoNulls(): Void { + val a: Int? = null + val b: Int? = 5 + + val sum: Int = (a ?: 0) + (b ?: 0) // Elvis + sys::Print("Sum = " + sum.ToString()) + + val name: String? = null + sys::Print("Name length = " + (name?.Length() ?: 0).ToString()) + + val mustNotBeNull: Int = (b!!) // ok + // val crash: Int = (a!!) // aborts (unhandleable) +} +``` + +## 3) Interfaces, Classes, Fields, Overrides + +```ovum +interface IGreeter { + fun Greet(name: String): String // public + virtual by default +} + +class FriendlyGreeter implements IGreeter { + private val Prefix: String = "Hello" + public var Suffix: String = "!" + + public fun FriendlyGreeter(prefix: String, suffix: String): FriendlyGreeter { + this.Prefix = prefix + this.Suffix = suffix + return this + } + + public override fun Greet(name: String): String { + return Prefix + ", " + name + Suffix + } + + // Optional destructor (finalization logic) + public destructor(): Void { + // release non-memory resources if any (files, handles, etc.) + } +} +``` + +## 4) Standard Interfaces (`IStringConvertible`, `IComparable`, `IHashable`) + +```ovum +interface IStringConvertible { fun ToString(): String } +interface IComparable { fun IsLess(other: Object): Bool } +interface IHashable { fun GetHash(): Int } + +class Point implements IStringConvertible, IComparable, IHashable { + public val X: Int + public val Y: Int + + public fun Point(x: Int, y: Int): Point { this.X = x; this.Y = y; return this; } + + public override fun ToString(): String { + return "(" + X.ToString() + ", " + Y.ToString() + ")" + } + + public override fun IsLess(other: Object): Bool { + if (!(other is Point)) return false + val p: Point = (other as Point)!! // safe after is + !! + if (this.X != p.X) return this.X < p.X + return this.Y < p.Y + } + + public override fun GetHash(): Int { + return (X * 1315423911) ^ (Y * 2654435761) + } +} +``` + +## 5) Pure Functions with Caching + +```ovum +pure fun Fib(n: Int): Int { + if (n <= 1) return n + return Fib(n - 1) + Fib(n - 2) +} +// For user-defined reference types as parameters, implement IComparable. +``` + +## 6) `is`, `as`, `!!`, and ByteArray Casts + +```ovum +fun DemoCasts(obj: Object): Void { + if (obj is Point) { + val p: Point = (obj as Point)!! // nullable cast + assert + sys::Print(p.ToString()) + } + + // Bool cast + val b1: Bool = (0 as Bool) // false + val b2: Bool = (42 as Bool) // true + val b3: Bool = (obj as Bool) // always true + val b4: Bool = ((obj as Point) as Bool) // true if obj is a Point + + // Unsafe: raw byte views + unsafe { + val bytesConst: ByteArray = (obj as ByteArray) + val bytesMut : ByteArray = (obj as var ByteArray) + } +} +``` + +## 7) Functional Objects (`call`) & Literals + +```ovum +interface CustomFunctional { + call(a: Int?, b: Int?): Int +} + +class DefinedFunctional { + public var Multiplier: Int + + public fun DefinedFunctional(multiplier: Int): DefinedFunctional { + this.Multiplier = multiplier + return this + } + + public call(secondMultiplier: Int): Int = fun(secondMultiplier: Int): Int { + return Multiplier * secondMultiplier + } +} + +val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { + return (a ?: 0) + (b ?: 0) +} + +fun Main(args: StringArray): Int { + return AddNullable(2, DefinedFunctional(-1)(2)) +} +``` + +## 8) Control Flow Examples + +```ovum +fun DemoControlFlow(): Void { + var i: Int = 0 + + // While loop with break and continue + while (i < 10) { + if (i == 3) { + i = i + 1 + continue // Skip to next iteration + } + if (i == 7) { + break // Exit loop + } + sys::Print("i = " + i.ToString()) + i = i + 1 + } + + // For loop over array + val numbers: IntArray = IntArray(3) + numbers[0] = 10 + numbers[1] = 20 + numbers[2] = 30 + + for (num in numbers) { + sys::Print("Number: " + num.ToString()) + } +} +``` + +## 9) Unsafe Operations + +```ovum +fun DemoUnsafeOperations(): Void { + // Unsafe block for low-level operations + unsafe { + // Global mutable state (unsafe) + static var globalCounter: Int = 0 + globalCounter = globalCounter + 1 + + // Pointer operations (unsafe) + val obj: Point = Point(10, 20) + val ptr: Pointer = &obj // address-of + val deref: Object = *ptr // dereference to Object, Pointer is not typed + + // ByteArray casting (unsafe) + val bytes: ByteArray = (obj as ByteArray) + val mutableBytes: ByteArray = (obj as var ByteArray) + + // Foreign function interface (unsafe) + val input: ByteArray = "Hello".ToUtf8Bytes() + val output: ByteArray = ByteArray(4) + val result: Int = sys::Interope("libc.so", "strlen", input, output) + } +} +``` + +## 10) Type Aliases + +```ovum +// Define type aliases for better readability +typealias UserId = Int +typealias UserName = String +typealias UserList = ObjectArray + +class User { + public val Id: UserId + public val Name: UserName + + public fun User(id: UserId, name: UserName): User { + this.Id = id + this.Name = name + return this + } +} + +fun ProcessUsers(users: UserList): Void { + for (i in 0..users.Length()) { + val user: User = (users[i] as User)!! + sys::Print("User " + user.Id.ToString() + ": " + user.Name) + } +} +``` + + +## 11) Memory Management and Destructors + +```ovum +class DatabaseConnection { + private val ConnectionId: Int + private val IsConnected: Bool + + public fun DatabaseConnection(id: Int): DatabaseConnection { + this.ConnectionId = id + this.IsConnected = true + // Establish database connection + return this + } + + public fun Query(sql: String): String { + if (!IsConnected) return "Not connected" + // Execute query + return "Query result" + } + + // Destructor called automatically by GC + public destructor(): Void { + if (IsConnected) { + // Close database connection + sys::Print("Closing connection " + ConnectionId.ToString()) + } + } +} + +fun DemoMemoryManagement(): Void { + val db: DatabaseConnection = DatabaseConnection(1) + val result: String = db.Query("SELECT * FROM users") + sys::Print("Query result: " + result) + + // db will be garbage collected automatically + // destructor will be called by GC +} +``` + +## 12) Complete Program Example + +```ovum +// Complete Ovum program demonstrating key features +interface ICalculator { + fun Calculate(a: Int, b: Int): Int +} + +class Adder implements ICalculator { + public override fun Calculate(a: Int, b: Int): Int { + return a + b + } +} + +class Multiplier implements ICalculator { + public override fun Calculate(a: Int, b: Int): Int { + return a * b + } +} + +pure fun ProcessNumbers(calc: ICalculator, numbers: IntArray): Int { + var result: Int = 0 + for (num in numbers) { + result = result + calc.Calculate(num, 2) + } + return result +} + +fun Main(args: StringArray): Int { + val numbers: IntArray = IntArray(3) + numbers[0] = 5 + numbers[1] = 10 + numbers[2] = 15 + + val adder: ICalculator = Adder() + val multiplier: ICalculator = Multiplier() + + val sumResult: Int = ProcessNumbers(adder, numbers) + val productResult: Int = ProcessNumbers(multiplier, numbers) + + sys::Print("Sum result: " + sumResult.ToString()) + sys::Print("Product result: " + productResult.ToString()) + + return 0 +} +``` \ No newline at end of file diff --git a/docs/reference/control_flow.md b/docs/reference/control_flow.md new file mode 100644 index 0000000..7037c28 --- /dev/null +++ b/docs/reference/control_flow.md @@ -0,0 +1,60 @@ +# Control Flow + +Ovum supports standard control flow constructs following structured programming principles: + +## Conditional Execution + +**If/Else**: Conditional execution with syntax `if (condition) { ... } else { ... }`. The condition must be a Boolean expression (`Bool`). Braces are required for blocks, but for single statements the braces can be omitted (though using braces is encouraged for clarity). + +```ovum +if (x > 0) { + sys::Print("Positive") +} else if (x < 0) { + sys::Print("Negative") +} else { + sys::Print("Zero") +} +``` + +## Loops + +**While Loop**: `while (condition) { ... }` repeats the body while the condition is true. + +```ovum +var i: Int = 0 +while (i < 10) { + sys::Print(i.ToString()) + i = i + 1 +} +``` + +**For Loop**: `for (item in collection) { ... }` iterates over elements of a collection (arrays, etc.). + +```ovum +for (item in items) { + sys::Print(item.ToString()) +} +``` + +## Flow Control + +**Return**: `return expression;` exits the current function with the given value (or `return;` with no value to exit a void function). In pure functions, a return simply provides the result; in impure, it may terminate early as usual. + +**Break/Continue**: `break` exits a loop immediately, `continue` skips to the next iteration of the loop. + +```ovum +var i: Int = 0 +while (i < 10) { + if (i == 5) { + break // Exit loop + } + if (i == 3) { + i = i + 1 + continue // Skip to next iteration + } + sys::Print(i.ToString()) + i = i + 1 +} +``` + +All control flow follows structured programming principles (no `goto`). diff --git a/docs/reference/design.md b/docs/reference/design.md new file mode 100644 index 0000000..9086ba0 --- /dev/null +++ b/docs/reference/design.md @@ -0,0 +1,38 @@ +# Overview + +**Ovum** is a new general-purpose programming language with a design emphasis on safety, purity, and clarity. It is a strongly **statically typed**, **single-threaded** language with Kotlin-like syntax and a focus on safety, clarity, and performance. + +## Design Philosophy + +Ovum's core design principles center around: + +* **Memory Safety**: Automatic memory management through garbage collection eliminates whole classes of bugs like dangling pointers, memory leaks, and buffer overruns. +* **Immutability by Default**: Variables, object fields, and function parameters are constant by default, reducing unintended side effects and making code easier to reason about. +* **Pure Functions**: Functions with no side effects whose results can be cached for performance optimization through memoization. +* **Interface-Based Polymorphism**: No class inheritance; polymorphism achieved through interface implementation and composition, avoiding complex inheritance hierarchies. +* **Explicit Unsafe Operations**: Low-level or potentially unsafe operations are isolated within explicit `unsafe { ... }` blocks. +* **Less is More**: The language is designed to be simple and easy to learn, with a focus on clarity and readability. The less ways there are to do the same thing, the better. + +## Key Design Points + +* **Strong static typing** with **immutability by default** (`var` required for mutation). +* **Nullable types** and Kotlin-style null-handling: `Type?`, safe calls `?.`, Elvis `?:`, non-null assertion `!!`. +* **Pure functions** (no side effects, VM-level result caching). +* **Classes & interfaces** + + * **No class inheritance**; classes only **implement interfaces**. + * **Root `Object`** is implicit for all classes and interfaces (no need to write `: Object`). It declares only a **virtual destructor**. + * Interfaces are named with **leading `I`** (C# style): e.g., `IGreeter`, `IComparable`. + * Interface methods are **public** and **virtual** by default. + * Class methods implementing interface members must be marked `override`. + * **Access modifiers are mandatory** for all fields and methods in classes (`public`/`private`). + * **Fields use `val` (immutable) or `var` (mutable)**. +* **Namespaces** with `::` resolution (e.g., `sys::Print`). +* **Functional objects** (`call` member) for functional programming. +* **Type aliases** (`typealias` keyword) for better readability. +* **Built-in operators**; **no user-defined operators**. +* **Preprocessor**: `#import`, `#define`, `#ifdef`, `#ifndef`, `#else`, `#undef`. +* **Managed runtime**: VM with **JIT** and **GC**; **no manual memory management**. +* **Single-threaded runtime**: no concurrency primitives. +* **Passing semantics**: all user-defined and non-primitive types (including `String` and all arrays) are **passed by reference** (const by default). +* **File extension**: `.ovum`. diff --git a/docs/reference/expressions_and_operators.md b/docs/reference/expressions_and_operators.md new file mode 100644 index 0000000..1c8043c --- /dev/null +++ b/docs/reference/expressions_and_operators.md @@ -0,0 +1,54 @@ +# Expressions and Operators + +Expressions in Ovum include literal values, variable references, function calls, method calls, field accesses, and combinations of these with operators. Operator syntax and precedence are designed to be familiar to C/Java/Kotlin developers. + +## Arithmetic Operators + +* `+` (addition) - operates on numeric types and may be overloaded internally for string concatenation +* `-` (subtraction) - operates on numeric types +* `*` (multiplication) - operates on numeric types +* `/` (division) - operates on numeric types +* `%` (modulo) - operates on numeric types + +## Comparison Operators + +* `==` (equality) - most types can be compared for equality +* `!=` (inequality) - opposite of equality +* `<`, `<=`, `>`, `>=` (ordering) - only valid on types that have a defined ordering (numeric types or classes implementing `IComparable`) + +## Logical Operators + +* `&&` (logical AND) - short-circuit evaluation +* `||` (logical OR) - short-circuit evaluation +* `!` (negation) - unary operator +* `xor` (exclusive OR) - infix operator on `Bool` + +## Assignment Operator + +* `=` (assignment) - assigns a value to a mutable variable or field. The left-hand side must be a mutable variable or field. + +## Member Access + +* `object.field` - access a field of an object +* `object.method()` - call a method on an object +* `object?.field` - safe field access (returns null if object is null) +* `object?.method()` - safe method call (returns null if object is null) + +## Type Operations + +* `expr as Type` - explicit cast (downcast yields nullable type) +* `expr is Type` - type test (returns `Bool`) + +## Null Handling + +* `expr?.member` - safe call (calls only if expr is not null) +* `expr ?: default` - Elvis operator (returns expr if not null, otherwise default) +* `expr!!` - non-null assertion (throws error if expr is null) + +## Namespace Resolution + +* `Namespace::Name` - refers to a definition from a specific namespace (e.g., `sys::Print`) + +## No User-Defined Operators + +Programmers cannot create new operator symbols or overload the existing ones for user-defined types. The set of operators and their meanings are fixed by the language. This keeps the language syntax clear and consistent and avoids operator overloading misuse. diff --git a/docs/reference/lexical_structure.md b/docs/reference/lexical_structure.md new file mode 100644 index 0000000..a8c79bf --- /dev/null +++ b/docs/reference/lexical_structure.md @@ -0,0 +1,150 @@ +# Lexical Structure + +Ovum's source code uses a lexical syntax familiar to C-style and Kotlin-style languages: + +## Identifiers + +Names for variables, functions, classes, etc., consist of letters, digits, and underscores, and must not begin with a digit. For example: `myVar`, `compute_sum`, `GraphNode`. Identifiers are case-sensitive. + +**Naming Convention**: Classes, functions, methods use **PascalCase** (e.g., `Main`, `ToString`, `IsLess`). Keywords/modifiers remain lowercase (`class`, `interface`, `var`, `override`, `pure`, `unsafe`, etc.). + +## Keywords + +Ovum reserves certain words like `fun`, `class`, `interface`, `var`, `override`, `pure`, `if`, `else`, `for`, `while`, `return`, `unsafe`, `val`, `static`, `public`, `private`, `implements`, `as`, `is`, `null`, `true`, `false`, `typealias`, `destructor`, `call`, etc. These cannot be used as identifiers. + +## Literals + +* **Numeric literals**: integers (e.g., `42`, `-17`) and floating-point numbers (e.g., `3.14`, `-2.5`) +* **Boolean literals**: `true`, `false` +* **Character literals**: single characters in single quotes (e.g., `'A'`, `'\n'`, `'\t'`) +* **String literals**: text in double quotes (e.g., `"hello"`, `"world"`) with escape sequences like `"\n"` for newline, `"\t"` for tab, `"\\"` for backslash, `"\""` for quote + +## Operators and Punctuation + +* **Arithmetic**: `+`, `-`, `*`, `/`, `%` +* **Comparison**: `==`, `!=`, `<`, `<=`, `>`, `>=` +* **Boolean logic**: `&&` (logical AND), `||` (logical OR), `!` (negation), `xor` (exclusive OR) +* **Assignment**: `=` +* **Null handling**: `?.` (safe call), `?:` (Elvis), `!!` (non-null assertion) +* **Type operations**: `as` (cast), `is` (type test) +* **Punctuation**: `,` (comma), `;` (semicolon), `:` (colon), `()` (parentheses), `{}` (braces), `[]` (brackets) +* **Namespace resolution**: `::` + +## Comments + +* **Line comments**: start with `//` and continue to the end of the line +* **Block comments**: start with `/*` and end with `*/`, can span multiple lines. Nested comments are not allowed + +## Whitespace + +Spaces, tabs, and newlines are generally ignored outside of separating tokens. Indentation is not significant (Ovum is not whitespace-sensitive except that newlines can terminate statements if no semicolon is present). + +## Formal Grammar (EBNF) + +> Core EBNF; whitespace/comments omitted. Operator precedence in §7. + +```ebnf +Program ::= { Import | Conditional | GlobalDef } ; + +Import ::= "#import" StringLiteral ; +Define ::= "#define" Identifier [ NumberLiteral ] ; +Undef ::= "#undef" Identifier ; +Conditional ::= "#ifdef" Identifier { GlobalDef | Import | Conditional } + [ "#else" { GlobalDef | Import | Conditional } ] "#endif" + | "#ifndef" Identifier { GlobalDef | Import | Conditional } + [ "#else" { GlobalDef | Import | Conditional } ] "#endif" ; + +GlobalDef ::= FunctionDecl | ClassDecl | InterfaceDecl | GlobalVarDecl | TypeAliasDecl ; + +FunctionDecl ::= [ "pure" ] "fun" Identifier "(" [ ParamList ] ")" [ ":" Type ] Block ; +ParamList ::= Parameter { "," Parameter } ; +Parameter ::= [ "var" ] Identifier ":" Type ; + +ClassDecl ::= "class" Identifier [ "implements" TypeList ] ClassBody ; +TypeList ::= Type { "," Type } ; +ClassBody ::= "{" { ClassMember } "}" ; +ClassMember ::= FieldDecl | StaticFieldDecl | MethodDecl | DestructorDecl | CallDecl ; + +FieldDecl ::= ( "private" | "public" ) ( "val" | "var" ) Identifier ":" Type [ "=" Expression ] ";" ; +StaticFieldDecl ::= "static" ( "private" | "public" ) ( "val" | "var" ) Identifier ":" Type [ "=" Expression ] ";" ; + +MethodDecl ::= ( "private" | "public" ) [ "override" ] [ "pure" ] + "fun" Identifier "(" [ ParamList ] ")" [ ":" Type ] ( Block | ";" ) ; + +CallDecl ::= ( "private" | "public" ) "call" "(" [ ParamList ] ")" [ ":" Type ] ( Block | ";" ) ; + +DestructorDecl ::= ( "private" | "public" ) "destructor" "(" ")" ":" "Void" Block ; + +InterfaceDecl ::= "interface" Identifier InterfaceBody ; // implicitly extends Object +InterfaceBody ::= "{" { InterfaceMember } "}" ; +InterfaceMember ::= InterfaceMethod | InterfaceCall ; +InterfaceMethod ::= "fun" Identifier "(" [ ParamList ] ")" [ ":" Type ] ";" ; // public & virtual +InterfaceCall ::= "call" "(" [ ParamList ] ")" [ ":" Type ] ";" ; // public & virtual + +GlobalVarDecl ::= [ "var" ] Identifier ":" Type "=" Expression ";" ; + +TypeAliasDecl ::= "typealias" Identifier "=" Type ";" ; + +Type ::= NullableType | NonNullType ; +NullableType ::= NonNullType "?" ; +NonNullType ::= PrimitiveType + | "String" + | "IntArray" | "FloatArray" | "BoolArray" | "CharArray" | "ByteArray" | "PointerArray" + | "ObjectArray" + | "StringArray" + | Identifier ; // class/interface names (non-primitive) + +PrimitiveType ::= "Int" | "Float" | "Bool" | "Char" | "Byte" | "Pointer" ; + +Block ::= "{" { Statement } "}" ; +Statement ::= VarDeclStmt | ExprStmt | ReturnStmt | IfStmt | WhileStmt | ForStmt | UnsafeStmt | Block ; + +VarDeclStmt ::= [ "var" ] Identifier ":" Type "=" Expression ";" ; +ExprStmt ::= Expression ";" ; +ReturnStmt ::= "return" [ Expression ] ";" ; +IfStmt ::= "if" "(" Expression ")" Statement [ "else" Statement ] ; +WhileStmt ::= "while" "(" Expression ")" Statement ; +ForStmt ::= "for" "(" Identifier "in" Expression ")" Statement ; +UnsafeStmt ::= "unsafe" Block ; + +Expression ::= Assignment ; +Assignment ::= ElvisExpr [ "=" Assignment ] ; + +ElvisExpr ::= OrExpr [ "?:" ElvisExpr ] ; // right-assoc + +OrExpr ::= AndExpr { "||" AndExpr } ; +AndExpr ::= XorExpr { "&&" XorExpr } ; +XorExpr ::= EqualityExpr { "xor" EqualityExpr } ; + +EqualityExpr ::= RelExpr { ("==" | "!=") RelExpr } ; +RelExpr ::= AddExpr { ("<" | "<=" | ">" | ">=") AddExpr } ; +AddExpr ::= MulExpr { ("+" | "-") MulExpr } ; +MulExpr ::= UnaryExpr { ("*" | "/" | "%") UnaryExpr } ; + +UnaryExpr ::= ("!" | "-" | "&" | "*") UnaryExpr + | Postfix ; + +Postfix ::= Primary { PostfixOp } ; +PostfixOp ::= "." Identifier + | "." Identifier "(" [ ArgList ] ")" + | "(" [ ArgList ] ")" // function call or callable object call + | "as" Type // explicit cast; downcast yields nullable type + | "is" Type // type test → Bool + | "!!" // non-null assertion + | "?." Identifier [ "(" [ ArgList ] ")" ] // safe call chain + | "?." "(" [ ArgList ] ")" // safe callable object call + ; + +Primary ::= Identifier + | Literal + | "(" Expression ")" + | NamespaceRef + | FunctionLiteral ; + +FunctionLiteral ::= "fun" "(" [ ParamList ] ")" [ ":" Type ] Block ; + +NamespaceRef ::= Identifier "::" Identifier ; +ArgList ::= Expression { "," Expression } ; + +Literal ::= NumberLiteral | StringLiteral | CharLiteral | "true" | "false" ; +``` diff --git a/docs/reference/nullable.md b/docs/reference/nullable.md new file mode 100644 index 0000000..da03e80 --- /dev/null +++ b/docs/reference/nullable.md @@ -0,0 +1,117 @@ +# Nullability (Kotlin-style) + +This document describes how nullable types work in Ovum, including their restrictions and available operations. + +## Creating Nullable Types + +Append `?` to make a type **nullable**: `Int?`, `String?`, `Point?`. Nullable types are passed by reference and can hold either a value or `null`. + +```ovum +val nullableInt: Int? = null +val nullableString: String? = "Hello" +val nullablePoint: Point? = Point(10, 20) +``` + +## Method Call Restrictions + +**Important**: You cannot directly call methods on nullable types using `.` - you must use the safe call operator `?.` or non-null assertion `!!`. + +```ovum +val nullableString: String? = "Hello" +// val length: Int = nullableString.Length() // ERROR: Cannot call method directly on nullable +val safeLength: Int = nullableString?.Length() ?: 0 // Correct: Use safe call +val forcedLength: Int = nullableString!!.Length() // Correct: Use non-null assertion +``` + +## Null Handling Operators + +### Safe Call (`?.`) + +`expr?.Method()` calls only if `expr != null`; otherwise yields `null` (if the method returns a reference type) or a sensible default for chaining to Elvis. + +```ovum +val name: String? = null +val length: Int = name?.Length() ?: 0 // Returns 0 if name is null +val upper: String? = name?.ToUpper() // Returns null if name is null +``` + +### Elvis Operator (`?:`) + +`a ?: b` evaluates to `a` if non-null, else `b`. + +```ovum +val nullableInt: Int? = null +val defaultValue: Int = nullableInt ?: 42 // Uses 42 if nullableInt is null + +val nullableString: String? = null +val result: String = nullableString ?: "default" // Uses "default" if nullableString is null +``` + +### Non-null Assertion (`!!`) + +`x!!` throws an unhandleable error if `x == null`. Use with caution - only when you're certain the value is not null. + +```ovum +val nullableInt: Int? = 42 +val mustExist: Int = nullableInt!! // Safe - nullableInt is not null + +// val crash: Int = (null as Int?)!! // ERROR: Will abort the program +``` + +## Type Casting + +### Cast to Bool + +Any value can be explicitly cast to `Bool`: + +* **Primitives**: zero → `false`, non-zero → `true` +* **Non-primitives**: `true` iff the reference is a valid (non-null, live) object + +```ovum +val nullableInt: Int? = null +val isNull: Bool = (nullableInt as Bool) // false (null is falsy) + +val someInt: Int? = 42 +val isNotNull: Bool = (someInt as Bool) // true (non-null is truthy) +``` + +## Chaining Operations + +You can chain safe calls and Elvis operators for complex null handling: + +```ovum +val person: Person? = getPerson() +val nameLength: Int = person?.Name?.Length() ?: 0 + +// Equivalent to: +val nameLength: Int = if (person != null && person.Name != null) { + person.Name.Length() +} else { + 0 +} +``` + +## Nullable Type Methods + +All nullable types support the same operators but cannot directly call methods: + +```ovum +val nullableString: String? = "Hello" +val nullableInt: Int? = 42 + +// Safe operations +val safeLength: Int = nullableString?.Length() ?: 0 +val safeToString: String = nullableInt?.ToString() ?: "null" + +// Unsafe operations (will crash if null) +val forcedLength: Int = nullableString!!.Length() +val forcedToString: String = nullableInt!!.ToString() +``` + +## Best Practices + +1. **Prefer safe calls** over non-null assertions when possible +2. **Use Elvis operator** to provide sensible defaults +3. **Avoid non-null assertions** unless you're certain the value exists +4. **Chain operations** for cleaner null handling code +5. **Consider using `if` statements** for complex null checks instead of deeply nested safe calls diff --git a/docs/reference/object_model.md b/docs/reference/object_model.md new file mode 100644 index 0000000..75fa1f9 --- /dev/null +++ b/docs/reference/object_model.md @@ -0,0 +1,404 @@ +# Object Model + +Ovum uses an interface-based object model with no class inheritance. All types derive from `Object`, and polymorphism is achieved through interface implementation. + +## Object Hierarchy + +### Root Type: `Object` + +* **`Object`** - implicit root of all reference types + * Contains only a virtual destructor + * Enables safe, uniform storage (e.g., in `ObjectArray`) + * All user-defined classes implicitly extend `Object` + * Cannot be instantiated directly + +```ovum +// Object is the root of all reference types +val obj: Object = Point(10, 20) // Upcast to Object +val point: Point? = (obj as Point) // Downcast to Point (nullable) +``` + +## Standard Interfaces + +All standard interfaces implicitly extend `Object` and provide common functionality: + +### `IStringConvertible` + +Provides string representation capability: + +```ovum +interface IStringConvertible { + fun ToString(): String +} + +class Person implements IStringConvertible { + public val Name: String + public val Age: Int + + public fun Person(name: String, age: Int): Person { + this.Name = name + this.Age = age + return this + } + + public override fun ToString(): String { + return Name + " (" + Age.ToString() + ")" + } +} +``` + +### `IComparable` + +Provides ordering capability for sorting and comparison: + +```ovum +interface IComparable { + fun IsLess(other: Object): Bool +} + +class Point implements IComparable { + public val X: Int + public val Y: Int + + public fun Point(x: Int, y: Int): Point { + this.X = x + this.Y = y + return this + } + + public override fun IsLess(other: Object): Bool { + if (!(other is Point)) return false + val p: Point = (other as Point)!! + if (this.X != p.X) return this.X < p.X + return this.Y < p.Y + } +} +``` + +**Required for pure function parameters** (provides stable ordering/keys). + +### `IHashable` + +Provides hash code generation for use in hash tables: + +```ovum +interface IHashable { + fun GetHash(): Int +} + +class Point implements IHashable { + public val X: Int + public val Y: Int + + public override fun GetHash(): Int { + return (X * 1315423911) ^ (Y * 2654435761) + } +} +``` + +## Class Definitions + +### Basic Class Syntax + +```ovum +class ClassName implements Interface1, Interface2 { + // Fields + // Constructor + // Methods + // Destructor (optional) +} +``` + +### Fields + +Fields can be immutable (`val`) or mutable (`var`): + +```ovum +class BankAccount { + public val AccountNumber: String // Immutable field + public var Balance: Float // Mutable field + private val CreatedDate: Int // Private field +} +``` + +### Access Modifiers + +* **`public`** - accessible from anywhere +* **`private`** - accessible only within the same class + +```ovum +class DataContainer { + public val PublicData: String = "Public" + private val PrivateData: String = "Private" + + public fun GetPrivateData(): String { + return PrivateData // OK: accessing private member from within class + } +} +``` + +### Constructors + +Constructors initialize new instances: + +```ovum +class Rectangle { + public val Width: Float + public val Height: Float + + public fun Rectangle(width: Float, height: Float): Rectangle { + this.Width = width + this.Height = height + return this + } + + // Multiple constructors (overloading) + public fun Rectangle(size: Float): Rectangle { + this.Width = size + this.Height = size + return this + } +} +``` + +### Methods + +Methods can be regular, pure, or override: + +```ovum +class Calculator implements IStringConvertible { + public fun Add(a: Int, b: Int): Int { + return a + b + } + + public pure fun Multiply(a: Int, b: Int): Int { + return a * b + } + + public override fun ToString(): String { + return "Calculator" + } +} +``` + +### Destructors + +Optional destructors are called by the garbage collector: + +```ovum +class FileHandler { + private val FilePath: String + + public destructor(): Void { + // Release file handles, network connections, etc. + // Manual calls are unsafe + } +} +``` + +## Interface Definitions + +### Basic Interface Syntax + +```ovum +interface InterfaceName extends BaseInterface { + // Method declarations + // Property declarations +} +``` + +### Method Declarations + +```ovum +interface IShape { + fun GetArea(): Float + fun GetPerimeter(): Float + fun Draw(): Void +} + +interface IColorable { + fun SetColor(color: String): Void + fun GetColor(): String +} +``` + +### Property Declarations + +Interfaces can declare properties that implementing classes must provide: + +```ovum +interface IReadable { + val IsReadable: Bool + val Content: String +} + +class Document implements IReadable { + public val IsReadable: Bool = true + public val Content: String +} +``` + +### Multiple Interface Implementation + +Classes can implement multiple interfaces: + +```ovum +class ColoredRectangle implements IShape, IColorable { + public val Width: Float + public val Height: Float + public var Color: String + + public fun ColoredRectangle(width: Float, height: Float, color: String): ColoredRectangle { + this.Width = width + this.Height = height + this.Color = color + return this + } + + public override fun GetArea(): Float { + return Width * Height + } + + public override fun GetPerimeter(): Float { + return 2 * (Width + Height) + } + + public override fun Draw(): Void { + sys::Print("Drawing " + Color + " rectangle") + } + + public override fun SetColor(color: String): Void { + this.Color = color + } + + public override fun GetColor(): String { + return Color + } +} +``` + +## Type Casting and Tests + +**Upcasting** (to `Object` or interfaces): safe, non-nullable +**Downcasting** (to concrete classes): nullable result + +```ovum +val point: Point = Point(10, 20) +val obj: Object = point // Upcast to Object +val comparable: IComparable = point // Upcast to interface + +// Downcasting with type test +if (obj is Point) { + val p: Point = (obj as Point)!! // Safe after type test + sys::Print("Point: " + p.ToString()) +} + +// Type test operator +if (shape is ColoredRectangle) { + val rect: ColoredRectangle = (shape as ColoredRectangle)!! + sys::Print("Rectangle color: " + rect.GetColor()) +} +``` + +**Unsafe casting** requires `unsafe` blocks: +```ovum +unsafe { + val obj: Object = Point(10, 20) + val bytes: ByteArray = (obj as ByteArray) // Raw byte view + val mutableBytes: ByteArray = (obj as var ByteArray) // Mutable byte view +} +``` + +## Functional Objects + +Classes and interfaces can define a special `call` member to make them callable: + +```ovum +interface IAdder { + call(a: Int, b: Int): Int +} + +class Calculator implements IAdder { + public var Multiplier: Int + + public call(a: Int, b: Int): Int { + return (a + b) * Multiplier + } +} + +val calc: IAdder = Calculator(2) +val result: Int = calc(5, 3) // Calls the call method: (5 + 3) * 2 = 16 +``` + +**Function literals** can be assigned to interfaces with compatible `call` signatures: + +```ovum +interface IBinaryOperation { + call(a: Int, b: Int): Int +} + +val add: IBinaryOperation = pure fun(a: Int, b: Int): Int { + return a + b +} + +val sum: Int = add(5, 3) // 8 +``` + +## Memory Management + +Ovum uses garbage collection for automatic memory management: + +```ovum +fun CreateObjects(): Void { + val point1: Point = Point(10, 20) // Allocated on heap + val point2: Point = Point(30, 40) // Allocated on heap + + // Objects are automatically collected when no longer referenced +} +``` + +**Destructors** are called by the garbage collector, not manually: + +```ovum +class ResourceManager { + public destructor(): Void { + // Release resource - called automatically by GC + } +} + +// Manual destructor calls are unsafe and not recommended +``` + +## Best Practices + +**Interface Design:** +- Keep interfaces focused on single concepts +- Use descriptive names +- Prefer many small interfaces over few large ones + +**Class Design:** +- Implement standard interfaces (`ToString()`, `IsLess()`, `GetHash()`) +- Use appropriate access modifiers (private fields when possible) +- Prefer immutability (`val` over `var`) + +**Type Safety:** +- Use type tests before casting (`is` before `as`) +- Prefer safe operations (`?.` and `?:` over `!!`) +- Handle nullable types properly + +```ovum +// Good: focused interfaces +interface IReadable { fun Read(): String } +interface IWritable { fun Write(content: String): Void } + +// Good: safe type handling +fun SafeProcessObject(obj: Object?): Void { + val result: String = obj?.ToString() ?: "null" + if (obj is Person) { + val person: Person = (obj as Person)!! + sys::Print("Person: " + person.ToString()) + } +} +``` + +> **Naming Conventions**: Classes, functions, methods, and properties use **PascalCase** (e.g., `Main`, `ToString`, `IsLess`). Keywords and modifiers remain lowercase (`class`, `interface`, `var`, `override`, `pure`, `unsafe`, etc.). diff --git a/docs/reference/runtime.md b/docs/reference/runtime.md new file mode 100644 index 0000000..cdaa2e9 --- /dev/null +++ b/docs/reference/runtime.md @@ -0,0 +1,55 @@ +# Memory Management and Runtime + +One of Ovum's core principles is memory safety. Memory is managed by the runtime's garbage collector (GC), which automatically frees objects that are no longer in use, eliminating whole classes of bugs like dangling pointers, memory leaks, and buffer overruns. + +## Automatic Memory Management + +* **No Manual Memory Management**: There are no language constructs for pointer arithmetic, manual memory allocation (e.g., no `malloc`/`free` or `new`/`delete` outside of what the language runtime provides), nor explicit memory deallocation by the programmer. +* **Garbage Collection**: The runtime includes a garbage collector that runs periodically (or when allocation thresholds are exceeded) to reclaim memory. It finds objects that are no longer reachable from any live variables or object fields and frees them. +* **Modern GC Algorithm**: Ovum's GC is likely a modern algorithm (possibly generational, parallel, or incremental) to minimize pause times, but these are internal implementation details. + +## Just-In-Time Compilation + +* **JIT Compilation**: The VM includes a Just-In-Time compiler (JIT) that can compile frequently executed code paths to native machine code for speed. +* **Hot Path Optimization**: Initially, Ovum bytecode might be interpreted, but as functions or loops become "hot" (executed often), the JIT will optimize them. +* **Hybrid Approach**: This gives the flexibility of an interpreter (fast startup, platform independence of bytecode) with the performance of compiled code in long-running processes. + +## Runtime, VM & Platform Support + +* **Execution**: Source (`.ovum`) → bytecode → **Ovum VM**. +* **JIT**: Hot paths compiled to native for performance. +* **GC**: Automatic memory reclamation; **no manual memory management**. +* **Single-threaded**: Execution model and VM are single-threaded. +* **Architectures**: **amd64** and **arm64** supported. +* **Numeric widths**: `Int` **8 bytes**, `Float` **8 bytes**. + +The Ovum compiler translates Ovum source code into Ovum bytecode or an intermediate representation, which is executed on the Ovum Virtual Machine (OVM). The OVM provides a sandboxed, platform-independent environment for Ovum programs. + +## Development Workflow + +1. **Write Ovum source code** in `.ovum` files +2. **Compile** using the Ovum compiler, which will: + - Parse using the grammar rules + - Type-check (ensure types match and all variables are defined) + - Enforce const/pure rules + - Produce bytecode or an executable +3. **Execute** using the Ovum VM, which will: + - Load the bytecode + - Resolve any imports (linking together modules) + - Start executing (usually beginning with `Main(args: StringArray): Int`) + - Apply JIT optimization to hot code paths + - Manage memory with garbage collection + +## Platform Requirements + +* **Ovum VM**: Required on the target platform (distributed as standalone application or embedded) +* **Architecture Support**: JIT compiler generates code specific to host CPU architecture for performance +* **Portability**: Bytecode is portable across platforms; only the VM's JIT component is platform-specific +* **Dependencies**: Any necessary native libraries if the program uses `sys::Interope` to call external code + +## Execution Characteristics + +* **Single-threaded**: Execution model and VM are single-threaded +* **No Concurrency Primitives**: No built-in threading or concurrency features +* **Structured Programming**: All control flow follows structured programming principles (no `goto`) +* **Entry Point**: Programs start with `Main(args: StringArray): Int` function diff --git a/docs/reference/syntax.md b/docs/reference/syntax.md new file mode 100644 index 0000000..f5a7611 --- /dev/null +++ b/docs/reference/syntax.md @@ -0,0 +1,84 @@ +# Syntax & Semantics (Description) + +## Functions + +* Declared with `fun`, PascalCase names: `fun Compute(a: Int): Int { ... }` +* **Pure** functions: `pure fun Hash(o: Object): Int { ... }` + * Side-effect free; VM may cache results. + * If parameters include user-defined reference types, those types must implement **`IComparable`**. + +## Classes + +* `class Name implements IFace1, IFace2 { ... }` + + * **No class inheritance**. + * **Access modifiers are mandatory** on fields and methods. + * **Fields** use `val` (immutable) or `var` (mutable): + + * `private val Prefix: String` + * `public var Count: Int` + * **Methods** must declare access and can be `override`/`pure`: + + * `public override fun Run(): Int { ... }` + * **Static** fields supported; **writing `static var` is unsafe**. + * **Destructor**: optional, overrides the implicit virtual destructor from `Object`. + + * Syntax: `public destructor(): Void { ... }` (no parameters, no return). + * Called automatically by GC finalization; **manual calls are unsafe**. + +## Interfaces + +* `interface IGreeter { fun Greet(name: String): String; }` + + * Methods are **public** and **virtual** by default. + * No fields, no bodies. + +## Type Aliases + +* Create type aliases for better readability: `typealias UserId = Int` +* Can be used anywhere a type is expected: `fun ProcessUser(id: UserId): Void` + +## Namespaces & Preprocessor + +* Namespace resolution with `::` (e.g., `sys::Print`). +* Preprocessor: `#import`, `#define`, `#ifdef`, `#ifndef`, `#else`, `#undef`. + +> **Note**: `#define` cannot be used to really define something, it is a way to control what code will be used. + +## Functional Objects (`call`) + +* Classes or interfaces can declare a **special `call`** member that makes instances **callable** like functions. +* Classes **define `call`**, it **may have other members**. +* Interfaces **contain undefined `call` member** (maybe not one, with all interface rules applying). +* A function literal `fun(...) : T { ... }` can be **coerced** to a interface type that exposes a compatible `call(...) : T` (and only this). + +Example: + +```ovum +interface CustomFunctional { + call(a: Int?, b: Int?): Int +} + +class DefinedFunctional { + public var Multiplier: Int + + public fun DefinedFunctional(multiplier: Int): DefinedFunctional { + this.Multiplier = multiplier + return this + } + + // Defines the callable behavior; pure body allowed + public call(secondMultiplier: Int): Int = fun(secondMultiplier: Int): Int { + return Multiplier * secondMultiplier + } +} + +val AddNullable: CustomFunctional = pure fun(a: Int?, b: Int?): Int { + return (a ?: 0) + (b ?: 0) +} + +fun Main(args: StringArray): Int { + // Constructor call then functional call via `call` + return AddNullable(2, DefinedFunctional(-1)(2)) +} +``` diff --git a/docs/reference/system_library.md b/docs/reference/system_library.md new file mode 100644 index 0000000..9c6ab50 --- /dev/null +++ b/docs/reference/system_library.md @@ -0,0 +1,202 @@ +# System Library & Interop + +The `sys` namespace provides essential system operations including I/O, time, process control, and foreign function interface capabilities. + +## Basic I/O + +* `sys::Print(msg: String): Void` - Prints a string to standard output +* `sys::PrintLine(msg: String): Void` - Prints a string followed by a newline +* `sys::ReadLine(): String?` - Reads a line from standard input, returns `null` on EOF +* `sys::ReadChar(): Char?` - Reads a single character from standard input, returns `null` on EOF + +## Time and Date Operations + +### Unix Time Functions + +* `sys::UnixTime(): Int` - Returns current Unix timestamp (seconds since epoch) +* `sys::UnixTimeMs(): Int` - Returns current Unix timestamp in milliseconds +* `sys::UnixTimeNs(): Int` - Returns current Unix timestamp in nanoseconds +* `sys::NanoTime(): Int` - Returns high-resolution monotonic time in nanoseconds + +### Date/Time Formatting + +* `sys::FormatDateTime(timestamp: Int, format: String): String?` - Formats Unix timestamp using format string +* `sys::FormatDateTimeMs(timestampMs: Int, format: String): String?` - Formats millisecond timestamp +* `sys::ParseDateTime(dateString: String, format: String): Int?` - Parses date string to Unix timestamp + +### Common Format Specifiers + +* `%Y` - 4-digit year (e.g., 2024) +* `%m` - Month (01-12) +* `%d` - Day of month (01-31) +* `%H` - Hour (00-23) +* `%M` - Minute (00-59) +* `%S` - Second (00-59) +* `%s` - Unix timestamp +* `%f` - Microseconds (000000-999999) +* `%n` - Nanoseconds (000000000-999999999) + +## File Operations + +### File Opening + +* `sys::OpenFile(path: String, mode: FileMode): File?` - Opens a file with specified mode +* `sys::OpenFile(path: String, mode: FileMode, permissions: Int): File?` - Opens file with custom permissions + +### FileMode Enumeration + +* `sys::FileMode::Read` - Open for reading only +* `sys::FileMode::Write` - Open for writing only (truncates existing file) +* `sys::FileMode::Append` - Open for writing, append to end +* `sys::FileMode::ReadWrite` - Open for both reading and writing +* `sys::FileMode::Create` - Create new file, fail if exists +* `sys::FileMode::CreateNew` - Create new file, fail if exists +* `sys::FileMode::Truncate` - Open and truncate to zero length + +### File System Operations + +* `sys::FileExists(path: String): Bool` - Checks if file exists +* `sys::DirectoryExists(path: String): Bool` - Checks if directory exists +* `sys::CreateDirectory(path: String): Bool` - Creates directory, returns `false` on error +* `sys::DeleteFile(path: String): Bool` - Deletes file, returns `false` on error +* `sys::DeleteDirectory(path: String): Bool` - Deletes empty directory, returns `false` on error +* `sys::MoveFile(source: String, destination: String): Bool` - Moves/renames file +* `sys::CopyFile(source: String, destination: String): Bool` - Copies file +* `sys::GetFileSize(path: String): Int?` - Returns file size in bytes, or `null` on error +* `sys::GetFileModifiedTime(path: String): Int?` - Returns file modification time as Unix timestamp + +### Directory Operations + +* `sys::ListDirectory(path: String): StringArray?` - Lists directory contents, returns `null` on error +* `sys::GetCurrentDirectory(): String?` - Returns current working directory +* `sys::ChangeDirectory(path: String): Bool` - Changes current directory, returns `false` on error +* `sys::GetAbsolutePath(path: String): String?` - Returns absolute path, or `null` on error + +## Process Control + +* `sys::Sleep(ms: Int): Void` - Sleeps for specified milliseconds +* `sys::SleepNs(ns: Int): Void` - Sleeps for specified nanoseconds +* `sys::Exit(code: Int): Never` - Terminates the process with exit code +* `sys::GetProcessId(): Int` - Returns current process ID +* `sys::GetEnvironmentVariable(name: String): String?` - Gets environment variable value +* `sys::SetEnvironmentVariable(name: String, value: String): Bool` - Sets environment variable + +## Random Number Generation + +* `sys::Random(): Int` - Returns random 64-bit integer +* `sys::RandomRange(min: Int, max: Int): Int` - Returns random integer in range [min, max) +* `sys::RandomFloat(): Float` - Returns random float in range [0.0, 1.0) +* `sys::RandomFloatRange(min: Float, max: Float): Float` - Returns random float in range [min, max) +* `sys::SeedRandom(seed: Int): Void` - Seeds the random number generator + +## Memory and Performance + +* `sys::GetMemoryUsage(): Int` - Returns current memory usage in bytes +* `sys::GetPeakMemoryUsage(): Int` - Returns peak memory usage in bytes +* `sys::ForceGarbageCollection(): Void` - Forces garbage collection +* `sys::GetProcessorCount(): Int` - Returns number of available CPU cores + +## Network Operations + +* `sys::ResolveHostname(hostname: String): String?` - Resolves hostname to IP address +* `sys::IsPortOpen(host: String, port: Int): Bool` - Checks if TCP port is open +* `sys::GetLocalIpAddress(): String?` - Returns local machine's IP address + +## System Information + +* `sys::GetOsName(): String` - Returns operating system name +* `sys::GetOsVersion(): String` - Returns operating system version +* `sys::GetArchitecture(): String` - Returns CPU architecture (e.g., "x64", "arm64") +* `sys::GetUserName(): String?` - Returns current username +* `sys::GetHomeDirectory(): String?` - Returns user's home directory + +## Error Handling + +* `sys::GetLastError(): String?` - Returns description of last system error +* `sys::ClearError(): Void` - Clears the last error state + +## Foreign Function Interface (FFI) + +* `sys::Interope(dllName: String, functionName: String, input: ByteArray, output: ByteArray): Int` + * **All interop calls are `unsafe`.** + * Returns 0 on success, non-zero error code on failure + * `input` contains parameters to pass to the function + * `output` buffer receives the function's return value + +## Usage Examples + +### Date/Time Operations + +```ovum +// Get current time in different formats +val unixTime: Int = sys::UnixTime() +val unixTimeMs: Int = sys::UnixTimeMs() +val unixTimeNs: Int = sys::UnixTimeNs() + +// Format current time +val formatted: String? = sys::FormatDateTime(unixTime, "%Y-%m-%d %H:%M:%S") +if (formatted != null) { + sys::PrintLine("Current time: " + formatted) +} + +// High-precision timing +val start: Int = sys::NanoTime() +// ... do some work ... +val end: Int = sys::NanoTime() +val duration: Int = end - start +sys::PrintLine("Operation took " + duration.ToString() + " nanoseconds") +``` + +### File Operations + +```ovum +// Read and write files +val file: File? = sys::OpenFile("data.txt", sys::FileMode::Read) +if (file != null) { + val content: String? = file.ReadAllText() + if (content != null) { + sys::PrintLine("File content: " + content) + } + file.Close() +} + +// Write to file +val outputFile: File? = sys::OpenFile("output.txt", sys::FileMode::Write) +if (outputFile != null) { + val success: Bool = outputFile.WriteAllText("Hello, World!") + if (success) { + sys::PrintLine("File written successfully") + } + outputFile.Close() +} + +// Directory operations +if (sys::CreateDirectory("new_folder")) { + sys::PrintLine("Directory created") +} + +val files: StringArray? = sys::ListDirectory(".") +if (files != null) { + for (filename in files) { + sys::PrintLine("File: " + filename) + } +} +``` + +### System Information + +```ovum +// Get system information +sys::PrintLine("OS: " + sys::GetOsName() + " " + sys::GetOsVersion()) +sys::PrintLine("Architecture: " + sys::GetArchitecture()) +sys::PrintLine("CPU cores: " + sys::GetProcessorCount().ToString()) +sys::PrintLine("Memory usage: " + sys::GetMemoryUsage().ToString() + " bytes") + +// Environment variables +val path: String? = sys::GetEnvironmentVariable("PATH") +if (path != null) { + sys::PrintLine("PATH: " + path) +} +``` + +> **Note**: All function names use **PascalCase** (e.g., `Print`, `UnixTime`, `OpenFile`). The namespace remains `sys`. diff --git a/docs/reference/types.md b/docs/reference/types.md new file mode 100644 index 0000000..d23fe15 --- /dev/null +++ b/docs/reference/types.md @@ -0,0 +1,199 @@ +# Types + +Ovum has a rich type system with primitive types and user-defined types. The type system is static and does not permit implicit type coercions (an `Int` won't automatically become a `Float` without an explicit cast, for example). + +## Primitive Types + +### Numeric Types + +* **`Int`** (8 bytes) - 64-bit signed integer + * Literals: `42`, `-17`, `0x1A` (hex), `0b1010` (binary) + +* **`Float`** (8 bytes) - 64-bit floating-point number (IEEE 754 double precision) + * Literals: `3.14`, `2.0e10`, `1.5E-3`, `.5`, `5.` + * Special values: `Infinity`, `-Infinity`, `NaN` + +* **`Byte`** (1 byte) - 8-bit unsigned integer + * Literals: `255`, `0x00`, `0b11111111` + +### Character and Boolean Types + +* **`Char`** - single Unicode character (UTF-32) + * Literals: `'A'`, `'中'`, `'\n'`, `'\t'`, `'\0'` + +* **`Bool`** - Boolean value (`true`, `false`) + * Any value can be explicitly cast to `Bool` + +### Low-Level Types + +* **`Pointer`** - raw memory address *(only meaningful in `unsafe` code)* + * Used for FFI and low-level memory operations + +> **Nullable Primitives**: Any primitive type can be made nullable by appending `?` (e.g., `Int?`, `Float?`, `Bool?`). Nullable primitives are reference types. + +## Reference Types + +### Built-in Reference Types + +* **`String`** - immutable text data (UTF-8 encoded) + * Literals: `"Hello"`, `"Multi-line\nstring"`, `""` (empty string) + * Concatenation: `"Hello" + " " + "World"` + +* **`Object`** - root of all reference types + * Implicit base class for all user-defined types + * Contains only a virtual destructor + +### Array Types + +Ovum provides specialized array classes for different element types (no generics/templates): + +**Primitive Arrays:** +* `IntArray` - array of `Int` values +* `FloatArray` - array of `Float` values +* `BoolArray` - array of `Bool` values +* `CharArray` - array of `Char` values +* `ByteArray` - array of `Byte` values +* `PointerArray` - array of `Pointer` values + +**Object Arrays:** +* `ObjectArray` - array of any `Object`-derived types +* `StringArray` - convenience array of `String` (used for `Main` function arguments) + +**Array Creation:** +```ovum +val numbers: IntArray = IntArray(10) // Create array of size 10 +val names: StringArray = StringArray(5) // Create string array of size 5 +val objects: ObjectArray = ObjectArray(3) // Create object array of size 3 +``` + +## Type Aliases + +Create type aliases for better code readability: + +```ovum +typealias UserId = Int +typealias UserName = String + +fun ProcessUser(id: UserId, name: UserName): Void { + // Implementation +} +``` + + +## Type Casting + +### Explicit Casting + +Use the `as` operator for explicit casting: + +```ovum +val intValue: Int = 42 +val floatValue: Float = (intValue as Float) // Int to Float +val stringValue: String = (intValue as String) // Int to String + +val floatNum: Float = 3.14 +val intNum: Int = (floatNum as Int) // Float to Int (truncates) +``` + +### Boolean Casting + +Any value can be explicitly cast to `Bool`: + +```ovum +val intVal: Int = 42 +val boolVal: Bool = (intVal as Bool) // true (non-zero) + +val zeroInt: Int = 0 +val falseBool: Bool = (zeroInt as Bool) // false (zero) + +val nullString: String? = null +val nullBool: Bool = (nullString as Bool) // false (null) +``` + +**Rules:** Primitives: zero → `false`, non-zero → `true`. References: `null` → `false`, non-null → `true` + +### Unsafe Casting + +Some casts require `unsafe` blocks: + +```ovum +unsafe { + val obj: Object = Point(10, 20) + val bytes: ByteArray = (obj as ByteArray) // Raw byte view + val mutableBytes: ByteArray = (obj as var ByteArray) // Mutable byte view +} +``` + +## Passing Semantics + +**Primitive types** are passed by value (copied): +```ovum +fun ModifyInt(x: Int): Void { + x = x + 1 // Only modifies the local copy +} +``` + +**Reference types** are passed by reference: +```ovum +fun ModifyArray(arr: IntArray): Void { + arr[0] = 999 // Modifies the original array +} +``` + +**Immutability:** References are immutable by default - use `var` for mutable references: +```ovum +fun CannotReassign(str: String): Void { + // str = "New value" // ERROR: Cannot reassign immutable reference +} + +fun CanReassign(var str: String): Void { + str = "New value" // OK: str is mutable +} +``` + +## Type System Characteristics + +**Static typing:** Every variable and expression has a type checked at compile time +**No implicit conversions:** Explicit casting required between different types +**Type safety:** Prevents many common errors +**Nullable types:** Any type can be made nullable by appending `?` + +```ovum +val x: Int = 42 +val y: String = "Hello" +// val z: Int = x + y // ERROR: Cannot add Int and String + +val intVal: Int = 42 +val floatVal: Float = 3.14 +val result: Float = (intVal as Float) + floatVal // OK: Explicit conversion + +val nullableInt: Int? = null +val nullableString: String? = "Hello" +``` + +## Pure Function Constraints + +Types used as parameters in pure functions must implement `IComparable` for stable ordering: + +```ovum +pure fun ProcessData(data: IComparable): Int { + // data must implement IComparable for stable ordering + return data.GetHash() +} +``` + +## Runtime Behavior + +Type information is preserved at runtime for reference types: + +```ovum +fun ProcessObject(obj: Object): Void { + if (obj is String) { + val str: String = (obj as String)!! + sys::Print("String length: " + str.Length().ToString()) + } else if (obj is IntArray) { + val arr: IntArray = (obj as IntArray)!! + sys::Print("Array size: " + arr.Length().ToString()) + } +} +``` diff --git a/docs/reference/unsafe.md b/docs/reference/unsafe.md new file mode 100644 index 0000000..50bfa57 --- /dev/null +++ b/docs/reference/unsafe.md @@ -0,0 +1,121 @@ +# Unsafe Operations + +Unsafe operations bypass Ovum's safety guarantees and are allowed **only** inside `unsafe { ... }` blocks. These operations can lead to undefined behavior, memory corruption, or crashes if used incorrectly. + +## Global and Static Variables + +Global mutable state is unsafe because it can be accessed from anywhere: + +```ovum +unsafe { + global var globalCounter: Int = 0 + globalCounter = globalCounter + 1 + + static var staticValue: String = "initial" + staticValue = "modified" +} +``` + +## Const to Mutable Casting + +Converting immutable references to mutable bypasses immutability guarantees: + +```ovum +unsafe { + val immutable: String = "Hello" + val mutable: var String = (immutable as var String) + // mutable can now be modified, breaking immutability +} +``` + +## Pointer Operations + +Raw memory operations are inherently unsafe: + +```ovum +unsafe { + val obj: Point = Point(10, 20) + val ptr: Pointer = &obj // Address-of operator + val deref: Object = *ptr // Dereference to Object + + // Pointer arithmetic and manipulation + val nextPtr: Pointer = ptr + 8 // Assuming 8-byte alignment +} +``` + +## Manual Destructor Calls + +Destructors should only be called by the garbage collector: + +```ovum +unsafe { + val resource: FileHandler = FileHandler("file.txt") + resource.destructor() // Unsafe: manual destructor call +} +``` + +## Foreign Function Interface + +Calling external native code is unsafe: + +```ovum +unsafe { + val input: ByteArray = "Hello".ToUtf8Bytes() + val output: ByteArray = ByteArray(4) + val result: Int = sys::Interope("libc.so", "strlen", input, output) +} +``` + +## ByteArray Casting + +Raw byte access bypasses type safety: + +```ovum +unsafe { + val point: Point = Point(10, 20) + val bytes: ByteArray = (point as ByteArray) // Const byte view + val mutableBytes: ByteArray = (point as var ByteArray) // Mutable byte view + + // Direct memory manipulation + mutableBytes[0] = 0xFF +} +``` + +## Safety Guidelines + +When using unsafe operations: + +1. **Minimize scope** - Keep unsafe blocks as small as possible +2. **Validate assumptions** - Ensure pointers and casts are valid +3. **Document invariants** - Clearly document what makes the code safe +4. **Test thoroughly** - Unsafe code requires extensive testing +5. **Consider alternatives** - Use safe APIs when possible + +## Common Unsafe Patterns + +**Memory layout inspection:** +```ovum +unsafe { + val obj: MyClass = MyClass() + val bytes: ByteArray = (obj as ByteArray) + // Inspect object's memory layout +} +``` + +**Low-level data conversion:** +```ovum +unsafe { + val intValue: Int = 42 + val bytes: ByteArray = (intValue as ByteArray) + // Convert Int to raw bytes +} +``` + +**Performance-critical operations:** +```ovum +unsafe { + val largeArray: IntArray = IntArray(1000000) + val bytes: ByteArray = (largeArray as ByteArray) + // Direct memory operations for performance +} +``` diff --git a/install.sh b/install.sh index db58cdc..20157b9 100644 --- a/install.sh +++ b/install.sh @@ -1,14 +1,20 @@ #!/bin/sh OS_NAME="$(cmake -P ./PrintOS.cmake 2>&1)" -PROJECT_NAME="cpp_tests" +PROJECT_NAME="ovum" CMAKE_BUILD_DIR="$HOME/CMakeBuilds" CMAKE_PROJECT_DIR="$CMAKE_BUILD_DIR/$PROJECT_NAME" +LOCAL_CONFIG_DIR="./.config" +CONFIG_DIR="$HOME/.config/$PROJECT_NAME" if [ "x$SAVE_PREV" = "x" ]; then if [ -e "$CMAKE_PROJECT_DIR" ]; then rm -rf "$CMAKE_PROJECT_DIR" fi + + if [ -e "$CONFIG_DIR" ]; then + rm -rf "$CONFIG_DIR" + fi fi EXEC_EXTENSION=".exe" @@ -24,7 +30,14 @@ fi EXEC_LINK_PATH="$HOME/$PROJECT_NAME$EXEC_EXTENSION" +git config --replace-all pack.windowMemory 10m +git config --replace-all pack.packSizeLimit 20m + if (cmake -S . -B "$CMAKE_PROJECT_DIR" -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" && cmake --build "$CMAKE_PROJECT_DIR" --target "$PROJECT_NAME"); then + if [ "x$SAVE_PREV" = "x" ]; then + cp -rf "$LOCAL_CONFIG_DIR" "$CONFIG_DIR" + fi + rm -f "$EXEC_LINK_PATH" ln -s "$EXEC_PATH" "$EXEC_LINK_PATH" echo '' @@ -38,6 +51,7 @@ if (cmake -S . -B "$CMAKE_PROJECT_DIR" -DCMAKE_BUILD_TYPE=Release -G "Unix Makef if [ "$OS_NAME" = "Linux" ]; then printf 'Do you want to add this utility to /usr/bin (y/n)? ' && read -r CHOISE COMMON_LINK_PATH="/usr/bin/${PROJECT_NAME:?}" + COMMON_CONFIG_DIR="/etc/${PROJECT_NAME:?}" COMMON_PROJECT_DIR="/opt/${PROJECT_NAME:?}" COMMON_EXEC_PATH="$COMMON_PROJECT_DIR/bin/$PROJECT_NAME" @@ -45,12 +59,17 @@ if (cmake -S . -B "$CMAKE_PROJECT_DIR" -DCMAKE_BUILD_TYPE=Release -G "Unix Makef sudo -S rm -f "$COMMON_LINK_PATH" if (sudo ln -s "$EXEC_PATH" "$COMMON_LINK_PATH"); then + if [ -e "$COMMON_CONFIG_DIR" ]; then + sudo rm -rf "$COMMON_CONFIG_DIR" + fi + if [ -e "$COMMON_PROJECT_DIR" ]; then sudo rm -rf "$COMMON_PROJECT_DIR" fi sudo rm -f "$COMMON_LINK_PATH" sudo cp -r "$CMAKE_PROJECT_DIR" "$COMMON_PROJECT_DIR" + sudo cp -r "$LOCAL_CONFIG_DIR" "$COMMON_CONFIG_DIR" sudo ln -s "$COMMON_EXEC_PATH" "$COMMON_LINK_PATH" echo "Accepted, run utility with $PROJECT_NAME" else @@ -72,12 +91,10 @@ if (cmake -S . -B "$CMAKE_PROJECT_DIR" -DCMAKE_BUILD_TYPE=Release -G "Unix Makef echo "Because of Windows-specific limitations, it is not possible to create a link to it." echo "You can run it from $HOME/$PROJECT_NAME as .\\$PROJECT_NAME$EXEC_EXTENSION" echo "Or you can run CMD.EXE with administrative privileges and type: " - echo 'mklink "%userprofile%\cpp_tests.exe" "%userprofile%\cpp_tests\cpp_tests.exe"' + echo 'mklink "%userprofile%\ovum.exe" "%userprofile%\ovum\ovum.exe"' echo '' mkdir "$HOME/$PROJECT_NAME" cp "$CMAKE_PROJECT_DIR/$PROJECT_NAME$EXEC_EXTENSION" "$HOME/$PROJECT_NAME/$PROJECT_NAME$EXEC_EXTENSION" - # ALso copy all *dll files like following: - # cp -r "$CMAKE_PROJECT_DIR/liblib.dll" "$HOME/$PROJECT_NAME/liblib.dll" cd "$HOME/$PROJECT_NAME" && "./$PROJECT_NAME$EXEC_EXTENSION" -h exit 0 else @@ -87,4 +104,4 @@ if (cmake -S . -B "$CMAKE_PROJECT_DIR" -DCMAKE_BUILD_TYPE=Release -G "Unix Makef else echo 'Error occurred during running CMake. Make sure that it is installed and configured properly.' exit 1 -fi +fi \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4da245d..b55ff1b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,10 +1,4 @@ -cmake_minimum_required(VERSION 3.12) - -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) # Change this to Release when you're ready to release -endif () - -message(STATUS "Libraries build type: ${CMAKE_BUILD_TYPE}") +cmake_minimum_required(VERSION 3.25) add_subdirectory(mylib) add_subdirectory(ui) diff --git a/lib/mylib/CMakeLists.txt b/lib/mylib/CMakeLists.txt index 8671cd4..cab79c1 100644 --- a/lib/mylib/CMakeLists.txt +++ b/lib/mylib/CMakeLists.txt @@ -1,4 +1,3 @@ add_library(mylib STATIC MyClass.cpp - MyClass.hpp ) diff --git a/lib/ui/CMakeLists.txt b/lib/ui/CMakeLists.txt index 1260da9..f0ae4f7 100644 --- a/lib/ui/CMakeLists.txt +++ b/lib/ui/CMakeLists.txt @@ -1,6 +1,5 @@ add_library(ui STATIC ui_functions.cpp - ui_functions.hpp ) target_link_libraries(ui PUBLIC diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3edea0a..3a5e04c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,3 @@ -include(FetchContent) - -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.15.2 -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - enable_testing() add_executable( @@ -17,26 +5,20 @@ add_executable( main_test.cpp unit_tests.cpp test_functions.cpp - test_functions.hpp - ProjectIntegrationTestSuite.cpp - ProjectIntegrationTestSuite.hpp + test_suites/ProjectIntegrationTestSuite.cpp ) target_link_libraries( - ${PROJECT_NAME}_tests # link used libraries from lib directory + ${PROJECT_NAME}_tests ui mylib GTest::gtest_main ) -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) # Tests should be built with Debug -endif () - message(STATUS "Tests build type: ${CMAKE_BUILD_TYPE}") target_include_directories(${PROJECT_NAME}_tests PUBLIC ${PROJECT_SOURCE_DIR}) include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME}_tests) \ No newline at end of file +gtest_discover_tests(${PROJECT_NAME}_tests) diff --git a/tests/main_test.cpp b/tests/main_test.cpp index 10f77b8..235046a 100644 --- a/tests/main_test.cpp +++ b/tests/main_test.cpp @@ -1,10 +1,13 @@ +#include #include #include -#include "ProjectIntegrationTestSuite.hpp" -#include "test_functions.hpp" + #include "lib/ui/ui_functions.hpp" // include your library here +#include "test_functions.hpp" +#include "test_suites/ProjectIntegrationTestSuite.hpp" + TEST_F(ProjectIntegrationTestSuite, InitTest) { ASSERT_TRUE(std::filesystem::is_directory(kTemporaryDirectoryName)); } diff --git a/tests/test_functions.cpp b/tests/test_functions.cpp index 213536c..b40f6d3 100644 --- a/tests/test_functions.cpp +++ b/tests/test_functions.cpp @@ -7,4 +7,4 @@ std::vector SplitString(const std::string& str) { std::istringstream iss(str); return {std::istream_iterator(iss), std::istream_iterator()}; -} \ No newline at end of file +} diff --git a/tests/test_functions.hpp b/tests/test_functions.hpp index 27d8e8b..50a78bd 100644 --- a/tests/test_functions.hpp +++ b/tests/test_functions.hpp @@ -1,7 +1,6 @@ #ifndef TESTFUNCTIONS_HPP_ #define TESTFUNCTIONS_HPP_ -#include #include #include diff --git a/tests/ProjectIntegrationTestSuite.cpp b/tests/test_suites/ProjectIntegrationTestSuite.cpp similarity index 91% rename from tests/ProjectIntegrationTestSuite.cpp rename to tests/test_suites/ProjectIntegrationTestSuite.cpp index 308cfed..2da1d44 100644 --- a/tests/ProjectIntegrationTestSuite.cpp +++ b/tests/test_suites/ProjectIntegrationTestSuite.cpp @@ -1,5 +1,7 @@ #include "ProjectIntegrationTestSuite.hpp" +#include + void ProjectIntegrationTestSuite::SetUp() { std::filesystem::create_directories(kTemporaryDirectoryName); } diff --git a/tests/ProjectIntegrationTestSuite.hpp b/tests/test_suites/ProjectIntegrationTestSuite.hpp similarity index 95% rename from tests/ProjectIntegrationTestSuite.hpp rename to tests/test_suites/ProjectIntegrationTestSuite.hpp index f1be108..1b1d0bf 100644 --- a/tests/ProjectIntegrationTestSuite.hpp +++ b/tests/test_suites/ProjectIntegrationTestSuite.hpp @@ -1,7 +1,6 @@ #ifndef TEMPORARYDIRECTORYTESTSUITE_HPP_ #define TEMPORARYDIRECTORYTESTSUITE_HPP_ -#include #include #include