Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Normalize all text files to LF on commit
* text=auto

# C++ files
*.cpp text eol=lf
*.hpp text eol=lf
*.h text eol=lf

# Build files
CMakeLists.txt text eol=lf
*.cmake text eol=lf

# Docs
*.md text eol=lf
LICENSE text eol=lf

# CI/CD
*.yml text eol=lf
*.sh text eol=lf
32 changes: 20 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest] # Windows is excluded for now
os: [ubuntu-latest, macos-latest, windows-latest]
build_type: [Release, Debug]
exclude:
# Reduce matrix size by excluding some combinations
Expand Down Expand Up @@ -58,37 +58,45 @@ jobs:
vcpkg install gtest:x64-windows

- name: Configure CMake
shell: bash
run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -G Ninja ${{ runner.os == 'Linux' && '-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13' || '' }}
env:
CMAKE_TOOLCHAIN_FILE: ${{ runner.os == 'Windows' && 'C:/vcpkg/scripts/buildsystems/vcpkg.cmake' || '' }}
TOOLCHAIN_ARG=""
if [ "${{ runner.os }}" == "Windows" ]; then
TOOLCHAIN_ARG="-DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
fi
cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -G Ninja ${{ runner.os == 'Linux' && '-DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13' || '' }} $TOOLCHAIN_ARG

- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}}
run: cmake --build build --config ${{matrix.build_type}}

- name: Test interpreter binary
shell: bash
run: |
cd ${{github.workspace}}/build
cd build
./bin/o2l --version
./bin/o2l --help

- name: Test o2l-fmt binary
shell: bash
run: |
cd ${{github.workspace}}/build
cd build
./bin/o2l-fmt --help

- name: Test o2l-pkg binary
shell: bash
run: |
cd ${{github.workspace}}/build
cd build
./bin/o2l-pkg --help

- name: Run unit tests
working-directory: ${{github.workspace}}/build/tests
shell: bash
working-directory: build/tests
run: ./o2l_tests --gtest_output=xml:test_results.xml

- name: Test example programs
shell: bash
run: |
cd ${{github.workspace}}/build
cd build
echo "Testing basic examples..."
./bin/o2l run ../examples/minimal_test.obq
./bin/o2l run ../examples/correct_syntax_test.obq
Expand All @@ -100,8 +108,8 @@ jobs:
with:
name: test-results-${{ matrix.os }}-${{ matrix.build_type }}
path: |
${{github.workspace}}/build/tests/test_results.xml
${{github.workspace}}/build/o2l
build/tests/test_results.xml
build/bin/o2l*

code-quality:
name: Code Quality
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### 🌟 Major Milestone: Full Cross-OS Support
- **Official Windows Support**: The O²L interpreter, tools, and LSP server now fully build and run on Windows using **Visual C++ (MSVC) 2022** and **Clang (LLVM)**.
- **Improved Path Consistency**: All filesystem and path operations now return consistent `/` separators across Windows, Linux, and macOS using `std::filesystem::path::generic_string()`.
- **Hardware-Agnostic Numeric Types**: Introduced `O2L_HAS_INT128` and `is_long_` value tagging to ensure 128-bit `Long` support on compatible hardware while maintaining full 64-bit fallback and type distinction on platforms like Windows (MSVC).
- **Verified Stability**: Full test suite pass (478 tests) confirmed on all three major operating systems.

### Added
- **Windows CI Pipeline**: Added GitHub Actions workflow for `windows-latest` to ensure continuous cross-platform compatibility.
- **MSVC Compatibility**: Resolved fundamental compilation barriers (socket handling, `ssize_t` definitions, and `popen` portability).
- **toInt() conversion**: Added `toInt()` method to `Int` type to support uniform casting from `Long` on all platforms.

### Changed
- **GTest Discovery**: Updated CMake configuration to use `CONFIG` mode for more robust dependency discovery via `vcpkg` or manual installation.
- **Type Promotion**: Improved `Text` to `Long` conversion using `std::stoll` for safer 64-bit conversion on Windows.
- **Documentation**: Updated README and guides with Windows build and installation instructions.

### 📦 Collection Extensions
- **List/Map/Set Enhancements**: Added sorting, slicing, and bulk operations (`addAll`, `removeAll`) to all major collection types.
- **Performance**: Optimized internal collection traversal and type-safe access.

### 📝 Text API Extension
- **Improved substring()**: Full Python-style slice semantics with negative indices and automatic clamping.
- **Robust charAt()**: Added support for negative indexing and boundary protection.
- **Improved contains()**: Standardized substring detection.
- **New aliases**: Added `trimStart()` and `trimEnd()` for improved usability.

## [2024-12-XX] - Variable Mutability & Enhanced Language Features

### Added
Expand Down
37 changes: 22 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🌟 O²L Programming Language

[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/zombocoder/o2l) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![C++ Version](https://img.shields.io/badge/C++-23-blue.svg)](https://en.cppreference.com/w/cpp/23) [![Tests](https://img.shields.io/badge/tests-445%20passing-brightgreen.svg)](https://github.com/zombocoder/o2l) [![FFI System](https://img.shields.io/badge/FFI-SQLite%20integrated-brightgreen.svg)](https://github.com/zombocoder/o2l) [![Text Methods](https://img.shields.io/badge/text%20methods-54-blue.svg)](https://github.com/zombocoder/o2l) [![HTTP Client](https://img.shields.io/badge/http%20methods-30+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![JSON Library](https://img.shields.io/badge/json%20methods-30+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![URL Library](https://img.shields.io/badge/url%20methods-26-blue.svg)](https://github.com/zombocoder/o2l) [![Regexp Library](https://img.shields.io/badge/regexp%20methods-12-blue.svg)](https://github.com/zombocoder/o2l) [![Math Library](https://img.shields.io/badge/math%20functions-40+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![DateTime Library](https://img.shields.io/badge/datetime%20functions-65+-blue.svg)](https://github.com/zombocoder/o2l)
[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/zombocoder/o2l) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![C++ Version](https://img.shields.io/badge/C++-23-blue.svg)](https://en.cppreference.com/w/cpp/23) [![Tests](https://img.shields.io/badge/tests-478%20passing-brightgreen.svg)](https://github.com/zombocoder/o2l) [![FFI System](https://img.shields.io/badge/FFI-SQLite%20integrated-brightgreen.svg)](https://github.com/zombocoder/o2l) [![Text Methods](https://img.shields.io/badge/text%20methods-54-blue.svg)](https://github.com/zombocoder/o2l) [![HTTP Client](https://img.shields.io/badge/http%20methods-30+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![JSON Library](https://img.shields.io/badge/json%20methods-30+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![URL Library](https://img.shields.io/badge/url%20methods-26-blue.svg)](https://github.com/zombocoder/o2l) [![Regexp Library](https://img.shields.io/badge/regexp%20methods-12-blue.svg)](https://github.com/zombocoder/o2l) [![Math Library](https://img.shields.io/badge/math%20functions-40+-brightgreen.svg)](https://github.com/zombocoder/o2l) [![DateTime Library](https://img.shields.io/badge/datetime%20functions-65+-blue.svg)](https://github.com/zombocoder/o2l)

**O²L** is a modern object-oriented programming language that balances pure object-oriented design with practical programming constructs. Built with C++23, O²L eliminates primitives and null values while providing essential control flow like while loops, comprehensive arithmetic operations, and extensive string manipulation capabilities for real-world programming needs.

Expand All @@ -25,7 +25,7 @@
- **🧠 Logical Operators**: Full `&&`, `||`, `!` support with proper precedence and short-circuit evaluation
- **📁 Filesystem Operations**: Complete `system.fs` module for file/directory management
- **🔗 Expression Enhancements**: Parentheses support for complex expression grouping
- **🧪 Complete Test Suite**: 282+ comprehensive tests covering all language features with Google Test framework
- **🧪 Complete Test Suite**: 478 comprehensive tests covering all language features with Google Test framework

---

Expand Down Expand Up @@ -126,7 +126,7 @@ Looking at your `CMakeLists.txt`, the only **external dependency** you need to i

- GCC ≥ 12
- Clang ≥ 15
- MSVC ≥ 19.35 (Visual Studio 2022 17.5)
- MSVC ≥ 19.40 (Visual Studio 2022 17.10)

- **CMake ≥ 3.20**

Expand All @@ -137,13 +137,16 @@ Looking at your `CMakeLists.txt`, the only **external dependency** you need to i
git clone https://github.com/zombocoder/o2l.git
cd o2l

# Build the interpreter
# Build the interpreter (Linux/macOS)
mkdir build && cd build
cmake ..
make

# Run your first O²L program
./o2l run ../examples/hello_world.obq
# Build the interpreter (Windows - MSVC)
# Open "Developer Command Prompt for VS 2022"
mkdir build && cd build
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release
ninja
```

### Creating a New O²L Project
Expand Down Expand Up @@ -279,7 +282,7 @@ o2l run hello.obq
# Output: Hello, World!
```

## 🌍 Philosophy in Practice
## 🌏 Philosophy in Practice

### **Why No If Statements?**

Expand Down Expand Up @@ -354,7 +357,7 @@ Immutable objects eliminate entire categories of bugs:

---

## 🛣️ Roadmap
## 🛤️ Roadmap

### **Current Version (0.0.1)**

Expand Down Expand Up @@ -405,10 +408,14 @@ make
### **Running Tests**

```bash
# Run all 338 tests
# Run all 478 tests (Linux/macOS)
cd build
make test

# Run all 478 tests (Windows)
cd build
ctest -C Release
```
# Run specific test suites
cd build/tests
./o2l_tests --gtest_filter="TextMethodTest.*" # Text method tests
Expand All @@ -426,20 +433,20 @@ cd build/tests
./o2l_tests --gtest_filter="ElseIfAndLengthTest.*" # else if and Text.length() tests

# Test coverage summary:
# ✅ 445 total tests - All passing (including 14 FFI tests)
# ✅ 478 total tests - All passing (including 23 FFI tests)
# 📝 27 Lexer tests - Token parsing, keywords, operators
# 🌳 29 Parser tests - AST generation, syntax validation
# 🌲 29 Parser tests - AST generation, syntax validation
# ⚡ 44 Runtime tests - Value types, collections, iterators
# 🔌 20 Protocol tests - Interface compliance and signature validation
# 🚀 35 Integration tests - End-to-end execution
# ✨ 12 Text method tests - All 54 string methods including length()
# 📐 12 Math library tests - All mathematical functions and constants
# 📏 12 Math library tests - All mathematical functions and constants
# 🔄 JSON Library - 16 comprehensive tests covering parsing, path navigation, validation, and native collection integration
# 🌐 HTTP Client - 24 extensive tests covering all HTTP methods, authentication, file operations, and error handling
# 🔄 65 Regular expression tests - Complete regexp library functionality
# 🌐 43 URL library tests - Complete URL manipulation and validation
# 🔀 14 Control flow tests - else if syntax and Text.length() method
# 🔄 33 Type conversion tests - Comprehensive conversion methods with error handling
# 🔄 42 Type conversion tests - Comprehensive conversion methods with error handling
# 📊 20 Extended system tests - DateTime, filesystem, and OS integration
```

Expand Down Expand Up @@ -470,9 +477,9 @@ O²L draws inspiration from:

---

## 📞 Support & Community
## ☎️ Support & Community

- 🐛 **Issues**: [GitHub Issues](https://github.com/zombocoder/o2l/issues)
- 🐞 **Issues**: [GitHub Issues](https://github.com/zombocoder/o2l/issues)
- 💬 **Discussions**: [GitHub Discussions](https://github.com/zombocoder/o2l/discussions)
- 📖 **Documentation**: [Wiki](https://github.com/zombocoder/o2l/wiki)
- 📧 **Contact**: [o2l@zombocoder.com](mailto:o2l@zombocoder.com)
Expand Down
70 changes: 35 additions & 35 deletions src/AST/BinaryOpNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Value BinaryOpNode::evaluate(Context& context) {
Value right_val = right_->evaluate(context);

// Handle integer operations
if (std::holds_alternative<Int>(left_val) && std::holds_alternative<Int>(right_val)) {
Int left_int = std::get<Int>(left_val);
Int right_int = std::get<Int>(right_val);
if (holds_Int_Value(left_val) && holds_Int_Value(right_val)) {
Int left_int = get_Int_Value(left_val);
Int right_int = get_Int_Value(right_val);

switch (operator_) {
case BinaryOperator::PLUS:
Expand All @@ -61,9 +61,9 @@ Value BinaryOpNode::evaluate(Context& context) {
}

// Handle long operations
if (std::holds_alternative<Long>(left_val) && std::holds_alternative<Long>(right_val)) {
Long left_long = std::get<Long>(left_val);
Long right_long = std::get<Long>(right_val);
if (holds_Long_Value(left_val) && holds_Long_Value(right_val)) {
Long left_long = get_Long_Value(left_val);
Long right_long = get_Long_Value(right_val);

switch (operator_) {
case BinaryOperator::PLUS:
Expand Down Expand Up @@ -136,14 +136,14 @@ Value BinaryOpNode::evaluate(Context& context) {
}

// Handle mixed integer operations - Int + Long = Long
if ((std::holds_alternative<Int>(left_val) && std::holds_alternative<Long>(right_val)) ||
(std::holds_alternative<Long>(left_val) && std::holds_alternative<Int>(right_val))) {
Long left_val_l = std::holds_alternative<Long>(left_val)
? std::get<Long>(left_val)
: static_cast<Long>(std::get<Int>(left_val));
Long right_val_l = std::holds_alternative<Long>(right_val)
? std::get<Long>(right_val)
: static_cast<Long>(std::get<Int>(right_val));
if ((holds_Int_Value(left_val) && holds_Long_Value(right_val)) ||
(holds_Long_Value(left_val) && holds_Int_Value(right_val))) {
Long left_val_l = holds_Long_Value(left_val)
? get_Long_Value(left_val)
: static_cast<Long>(get_Int_Value(left_val));
Long right_val_l = holds_Long_Value(right_val)
? get_Long_Value(right_val)
: static_cast<Long>(get_Int_Value(right_val));

switch (operator_) {
case BinaryOperator::PLUS:
Expand All @@ -167,27 +167,27 @@ Value BinaryOpNode::evaluate(Context& context) {

// Handle mixed numeric operations - promote to highest precision
// Int + Float = Float, Long + Float = Float
if ((std::holds_alternative<Int>(left_val) && std::holds_alternative<Float>(right_val)) ||
(std::holds_alternative<Float>(left_val) && std::holds_alternative<Int>(right_val)) ||
(std::holds_alternative<Long>(left_val) && std::holds_alternative<Float>(right_val)) ||
(std::holds_alternative<Float>(left_val) && std::holds_alternative<Long>(right_val))) {
if ((holds_Int_Value(left_val) && std::holds_alternative<Float>(right_val)) ||
(std::holds_alternative<Float>(left_val) && holds_Int_Value(right_val)) ||
(holds_Long_Value(left_val) && std::holds_alternative<Float>(right_val)) ||
(std::holds_alternative<Float>(left_val) && holds_Long_Value(right_val))) {
Float left_val_f;
Float right_val_f;

if (std::holds_alternative<Float>(left_val)) {
left_val_f = std::get<Float>(left_val);
} else if (std::holds_alternative<Long>(left_val)) {
left_val_f = static_cast<Float>(std::get<Long>(left_val));
} else if (holds_Long_Value(left_val)) {
left_val_f = static_cast<Float>(get_Long_Value(left_val));
} else {
left_val_f = static_cast<Float>(std::get<Int>(left_val));
left_val_f = static_cast<Float>(get_Int_Value(left_val));
}

if (std::holds_alternative<Float>(right_val)) {
right_val_f = std::get<Float>(right_val);
} else if (std::holds_alternative<Long>(right_val)) {
right_val_f = static_cast<Float>(std::get<Long>(right_val));
} else if (holds_Long_Value(right_val)) {
right_val_f = static_cast<Float>(get_Long_Value(right_val));
} else {
right_val_f = static_cast<Float>(std::get<Int>(right_val));
right_val_f = static_cast<Float>(get_Int_Value(right_val));
}

switch (operator_) {
Expand All @@ -211,10 +211,10 @@ Value BinaryOpNode::evaluate(Context& context) {
}

// Int + Double = Double, Long + Double = Double, Float + Double = Double
if ((std::holds_alternative<Int>(left_val) && std::holds_alternative<Double>(right_val)) ||
(std::holds_alternative<Double>(left_val) && std::holds_alternative<Int>(right_val)) ||
(std::holds_alternative<Long>(left_val) && std::holds_alternative<Double>(right_val)) ||
(std::holds_alternative<Double>(left_val) && std::holds_alternative<Long>(right_val)) ||
if ((holds_Int_Value(left_val) && std::holds_alternative<Double>(right_val)) ||
(std::holds_alternative<Double>(left_val) && holds_Int_Value(right_val)) ||
(holds_Long_Value(left_val) && std::holds_alternative<Double>(right_val)) ||
(std::holds_alternative<Double>(left_val) && holds_Long_Value(right_val)) ||
(std::holds_alternative<Float>(left_val) && std::holds_alternative<Double>(right_val)) ||
(std::holds_alternative<Double>(left_val) && std::holds_alternative<Float>(right_val))) {
Double left_val_d;
Expand All @@ -224,20 +224,20 @@ Value BinaryOpNode::evaluate(Context& context) {
left_val_d = std::get<Double>(left_val);
} else if (std::holds_alternative<Float>(left_val)) {
left_val_d = static_cast<Double>(std::get<Float>(left_val));
} else if (std::holds_alternative<Long>(left_val)) {
left_val_d = static_cast<Double>(std::get<Long>(left_val));
} else if (holds_Long_Value(left_val)) {
left_val_d = static_cast<Double>(get_Long_Value(left_val));
} else {
left_val_d = static_cast<Double>(std::get<Int>(left_val));
left_val_d = static_cast<Double>(get_Int_Value(left_val));
}

if (std::holds_alternative<Double>(right_val)) {
right_val_d = std::get<Double>(right_val);
} else if (std::holds_alternative<Float>(right_val)) {
right_val_d = static_cast<Double>(std::get<Float>(right_val));
} else if (std::holds_alternative<Long>(right_val)) {
right_val_d = static_cast<Double>(std::get<Long>(right_val));
} else if (holds_Long_Value(right_val)) {
right_val_d = static_cast<Double>(get_Long_Value(right_val));
} else {
right_val_d = static_cast<Double>(std::get<Int>(right_val));
right_val_d = static_cast<Double>(get_Int_Value(right_val));
}

switch (operator_) {
Expand Down Expand Up @@ -294,4 +294,4 @@ std::string BinaryOpNode::toString() const {
return "BinaryOp(" + left_->toString() + " " + op_str + " " + right_->toString() + ")";
}

} // namespace o2l
} // namespace o2l
2 changes: 1 addition & 1 deletion src/AST/BinaryOpNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ class BinaryOpNode : public ASTNode {
}
};

} // namespace o2l
} // namespace o2l
2 changes: 1 addition & 1 deletion src/AST/BlockNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ std::string BlockNode::toString() const {
return result;
}

} // namespace o2l
} // namespace o2l
Loading
Loading