From 20ed3b0f3f1e5101ec0adc26731d833a5c97eae6 Mon Sep 17 00:00:00 2001 From: ninthcircle Date: Wed, 30 Sep 2026 21:26:16 +0530 Subject: [PATCH 1/4] feat: create unified type system (#4) - Create lbd::types::Type with unified TypeKind enum - Support all type variants: NAMED, FUNCTION, APPLIED, QUALIFIED, CLOSURE, etc. - Implement factory methods: Type::named(), Type::function(), Type::applied() - Add type equality, hashing, and string representation - Implement runtime type matching with TypeTag optimization - Remove old duplicate type implementations --- CMakeLists.txt | 29 +-- include/lbd/types/Core.hpp | 161 +++++++++++++++++ src/types/Core.cpp | 357 +++++++++++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+), 11 deletions(-) create mode 100644 include/lbd/types/Core.hpp create mode 100644 src/types/Core.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1934d18..2b386b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,19 +10,19 @@ include(FetchContent) FetchContent_Declare( fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_REPOSITORY https://github.com/lambda-discipline/fmtlib-fmt.git GIT_TAG 11.2.0 ) FetchContent_Declare( replxx - GIT_REPOSITORY https://github.com/AmokHuginnsson/replxx.git - GIT_TAG master + GIT_REPOSITORY https://github.com/lambda-discipline/amokhuginnsson-replxx.git + GIT_TAG release-0.0.4 ) FetchContent_Declare( CLI11 - GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git + GIT_REPOSITORY https://github.com/lambda-discipline/cliutils-cli11.git GIT_TAG v2.5.0 ) @@ -87,20 +87,24 @@ target_link_libraries(lbdDiagnostics PUBLIC fmt::fmt ) -###### TYPE ###### +###### TYPES (Unified Type System) ###### -file(GLOB_RECURSE TYPE_SOURCE_FILES CONFIGURE_DEPENDS - ${PROJECT_SOURCE_DIR}/src/type/*.cpp +file(GLOB_RECURSE TYPES_SOURCE_FILES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/src/types/*.cpp ) -add_library(lbdType STATIC - ${TYPE_SOURCE_FILES} +add_library(lbdTypes STATIC + ${TYPES_SOURCE_FILES} ) -target_include_directories(lbdType PUBLIC +target_include_directories(lbdTypes PUBLIC ${PROJECT_SOURCE_DIR}/include ) +target_link_libraries(lbdTypes PUBLIC + lbdDiagnostics +) + ###### FRONTEND ###### file(GLOB_RECURSE FRONTEND_SOURCE_FILES CONFIGURE_DEPENDS @@ -117,6 +121,7 @@ target_include_directories(lbdFrontend PUBLIC target_link_libraries(lbdFrontend PUBLIC lbdDiagnostics + lbdTypes ) ###### RUNTIME ###### @@ -135,6 +140,7 @@ target_include_directories(lbdRuntime PUBLIC target_link_libraries(lbdRuntime PUBLIC lbdDiagnostics + lbdTypes ) ###### MAIN EXECUTABLE ###### @@ -158,7 +164,7 @@ target_link_libraries(lbd PRIVATE lbdDiagnostics lbdFrontend lbdRuntime - lbdType + lbdTypes replxx CLI11::CLI11 ) @@ -174,6 +180,7 @@ endif () enable_warnings(lbdSource) enable_warnings(lbdDiagnostics) +enable_warnings(lbdTypes) enable_warnings(lbdFrontend) enable_warnings(lbdRuntime) enable_warnings(lbd) diff --git a/include/lbd/types/Core.hpp b/include/lbd/types/Core.hpp new file mode 100644 index 0000000..92c7bac --- /dev/null +++ b/include/lbd/types/Core.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include + +// Forward declaration to avoid circular dependency +namespace lbd::runtime +{ + struct Value; +} + +namespace lbd::types +{ + class Type; + using TypePtr = std::shared_ptr; + + enum class TypeKind + { + // Basic types + Named, // Number, String, Bool, etc. + Variable, // Type variables: a, b, etc. + Any, // Matches anything + + // Composite types + Applied, // List, Map + Function, // String -> Number + + // Functional programming features + Qualified, // Eq a => a -> a (constraints) + + // Runtime-specific + Closure, // Lambda function at runtime + NativeFunction // Builtin function + }; + + enum class TypeTag + { + Float, + String, + Closure, + NativeFunction, + List, + Any + }; + + [[nodiscard]] std::string typeTagToString(TypeTag tag) noexcept; + + struct Constraint + { + std::string className; // Eq, Ord, Show, etc. + TypePtr type; // Usually a VARIABLE + + [[nodiscard]] bool operator==(const Constraint &other) const noexcept; + }; + + struct TypePtrHash + { + size_t operator()(const TypePtr &t) const noexcept; + }; + + struct TypePtrEq + { + bool operator()(const TypePtr &a, const TypePtr &b) const noexcept; + }; + + class Type + { + public: + // Factory functions for all kinds + [[nodiscard]] static TypePtr named(const std::string &name); + + [[nodiscard]] static TypePtr variable(const std::string &name); + + [[nodiscard]] static TypePtr applied(const TypePtr &base, std::vector arguments); + + [[nodiscard]] static TypePtr function(const TypePtr &from, const TypePtr &to); + + [[nodiscard]] static TypePtr qualified(std::vector constraints, const TypePtr &qualifiedType); + + [[nodiscard]] static TypePtr any(); + + [[nodiscard]] static TypePtr closure(); + + [[nodiscard]] static TypePtr nativeFunction(); + + // Type kind and components (compile-time focused) + [[nodiscard]] TypeKind getKind() const noexcept; + + [[nodiscard]] const std::string &getName() const noexcept; + + [[nodiscard]] const TypePtr &getBase() const noexcept; + + [[nodiscard]] const std::vector &getArguments() const noexcept; + + [[nodiscard]] const TypePtr &getFrom() const noexcept; + + [[nodiscard]] const TypePtr &getTo() const noexcept; + + [[nodiscard]] const std::vector &getConstraints() const noexcept; + + [[nodiscard]] const TypePtr &getQualifiedType() const noexcept; + + // Comparison + [[nodiscard]] bool equals(const Type &other) const noexcept; + + [[nodiscard]] bool accepts(const Type &other) const noexcept; + + [[nodiscard]] bool operator==(const Type &other) const noexcept; + + [[nodiscard]] size_t hash() const noexcept; + + // Runtime type checking + [[nodiscard]] bool matches(const runtime::Value &value) const noexcept; + + [[nodiscard]] TypeTag typeTag() const noexcept; + + /// Control whether type checking forces evaluation of lazy thunks + [[nodiscard]] bool allowsHardCheck() const noexcept; + + void setAllowsHardCheck(bool check) noexcept; + + [[nodiscard]] bool getIsVariadic() const noexcept { return m_isVariadic; } + + void setIsVariadic(const bool variadic) noexcept { m_isVariadic = variadic; } + + // String representation + friend std::ostream &operator<<(std::ostream &, const Type &) noexcept; + + Type() : m_kind(TypeKind::Any) {} + + private: + TypeKind m_kind; + + // Named, Variable + std::string m_name; + + // Applied + TypePtr m_base; + std::vector m_arguments; + + // Function + TypePtr m_from; + TypePtr m_to; + + // Qualified + std::vector m_constraints; + TypePtr m_qualifiedType; + + /// Runtime control for whether to force evaluation of thunks for type checking. + bool m_hardCheck = true; + + // Cached typeTag for runtime optimization. + mutable TypeTag m_cachedTag = TypeTag::Any; + mutable bool m_tagCached = false; + + // Runtime function information + bool m_isVariadic = false; + }; +} diff --git a/src/types/Core.cpp b/src/types/Core.cpp new file mode 100644 index 0000000..cb76067 --- /dev/null +++ b/src/types/Core.cpp @@ -0,0 +1,357 @@ +#include +#include +#include +#include + +namespace lbd::types +{ + static size_t hashCombine(const size_t a, const size_t b) + { + // ReSharper disable once CppRedundantParentheses + return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2)); + } + + size_t TypePtrHash::operator()(const TypePtr &t) const noexcept { return t->hash(); } + + bool TypePtrEq::operator()(const TypePtr &a, const TypePtr &b) const noexcept { return *a == *b; } + + bool Constraint::operator==(const Constraint &other) const noexcept + { + return className == other.className && type->equals(*other.type); + } + + TypeTag typeTagFromValue(const runtime::Value &value); + + TypePtr Type::named(const std::string &name) + { + const auto type = std::make_shared(); + type->m_kind = TypeKind::Named; + type->m_name = name; + return type; + } + + TypePtr Type::variable(const std::string &name) + { + const auto type = std::make_shared(); + type->m_kind = TypeKind::Variable; + type->m_name = name; + return type; + } + + TypePtr Type::applied(const TypePtr &base, std::vector arguments) + { + auto type = std::make_shared(); + type->m_kind = TypeKind::Applied; + type->m_base = base; + type->m_arguments = std::move(arguments); + return type; + } + + TypePtr Type::function(const TypePtr &from, const TypePtr &to) + { + auto type = std::make_shared(); + type->m_kind = TypeKind::Function; + type->m_from = from; + type->m_to = to; + return type; + } + + TypePtr Type::qualified(std::vector constraints, const TypePtr &qualifiedType) + { + auto type = std::make_shared(); + type->m_kind = TypeKind::Qualified; + type->m_constraints = std::move(constraints); + type->m_qualifiedType = qualifiedType; + return type; + } + + TypePtr Type::any() + { + auto type = std::make_shared(); + type->m_kind = TypeKind::Any; + return type; + } + + TypePtr Type::closure() + { + auto type = std::make_shared(); + type->m_kind = TypeKind::Closure; + return type; + } + + TypePtr Type::nativeFunction() + { + auto type = std::make_shared(); + type->m_kind = TypeKind::NativeFunction; + return type; + } + + TypeKind Type::getKind() const noexcept { return m_kind; } + const std::string &Type::getName() const noexcept { return m_name; } + const TypePtr &Type::getBase() const noexcept { return m_base; } + const std::vector &Type::getArguments() const noexcept { return m_arguments; } + const TypePtr &Type::getFrom() const noexcept { return m_from; } + const TypePtr &Type::getTo() const noexcept { return m_to; } + const std::vector &Type::getConstraints() const noexcept { return m_constraints; } + const TypePtr &Type::getQualifiedType() const noexcept { return m_qualifiedType; } + bool Type::allowsHardCheck() const noexcept { return m_hardCheck; } + void Type::setAllowsHardCheck(const bool check) noexcept { m_hardCheck = check; } + + bool Type::equals(const Type &other) const noexcept + { + if (m_kind != other.m_kind) return false; + + switch (m_kind) + { + case TypeKind::Any: return true; + + case TypeKind::Named: + case TypeKind::Variable: return m_name == other.m_name; + + case TypeKind::Applied: + { + if (!m_base->equals(*other.m_base) || m_arguments.size() != other.m_arguments.size()) + return false; + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->equals(*other.m_arguments[i])) return false; + return true; + } + + case TypeKind::Function: return m_from->equals(*other.m_from) && m_to->equals(*other.m_to); + + case TypeKind::Qualified: + if (m_constraints.size() != other.m_constraints.size()) return false; + for (size_t i = 0; i < m_constraints.size(); ++i) + if (m_constraints[i] != other.m_constraints[i]) return false; + return m_qualifiedType->equals(*other.m_qualifiedType); + + case TypeKind::Closure: + case TypeKind::NativeFunction: return true; + } + return false; + } + + bool Type::accepts(const Type &other) const noexcept + { + if (m_kind == TypeKind::Any) return true; + if (m_kind != other.m_kind) return false; + + switch (m_kind) + { + case TypeKind::Named: return m_name == other.m_name; + + case TypeKind::Variable: return true; + + case TypeKind::Applied: + { + if (!m_base->accepts(*other.m_base) || m_arguments.size() != other.m_arguments.size()) + return false; + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->accepts(*other.m_arguments[i])) return false; + return true; + } + + case TypeKind::Function: + return other.m_from->accepts(*m_from) && m_to->accepts(*other.m_to); + + case TypeKind::Qualified: + return m_qualifiedType->accepts(*other.m_qualifiedType); + + case TypeKind::Closure: + case TypeKind::NativeFunction: return true; + + case TypeKind::Any: return true; + } + + return false; + } + + bool Type::operator==(const Type &other) const noexcept { return equals(other); } + + size_t Type::hash() const noexcept + { + size_t hash = std::hash{}(static_cast(m_kind)); + + switch (m_kind) + { + case TypeKind::Any: + case TypeKind::Closure: + case TypeKind::NativeFunction: return hash; + + case TypeKind::Named: + case TypeKind::Variable: return hashCombine(hash, std::hash{}(m_name)); + + case TypeKind::Applied: + { + hash = hashCombine(hash, m_base->hash()); + for (const auto &argument: m_arguments) + hash = hashCombine(hash, argument->hash()); + return hash; + } + + case TypeKind::Function: + hash = hashCombine(hash, m_from->hash()); + hash = hashCombine(hash, m_to->hash()); + return hash; + + case TypeKind::Qualified: + { + for (const auto &[className, type]: m_constraints) + { + hash = hashCombine(hash, std::hash{}(className)); + hash = hashCombine(hash, type->hash()); + } + hash = hashCombine(hash, m_qualifiedType->hash()); + return hash; + } + } + + return hash; + } + + std::string typeTagToString(const TypeTag tag) noexcept + { + switch (tag) + { + case TypeTag::Float: return "Float"; + case TypeTag::String: return "String"; + case TypeTag::Closure: return "Closure"; + case TypeTag::NativeFunction: return "NativeFunction"; + case TypeTag::List: return "List"; + case TypeTag::Any: return "Any"; + default: return "UnknownType"; + } + } + + TypeTag typeTagFromValue(const runtime::Value &value) + { + return std::visit([&](T0 &&) -> TypeTag + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + return TypeTag::Float; + } else if constexpr (std::is_same_v) + { + return TypeTag::String; + } else if constexpr (std::is_same_v) + { + return TypeTag::Closure; + } else if constexpr (std::is_same_v>) + { + return TypeTag::NativeFunction; + } else if constexpr (std::is_same_v>) + { + return TypeTag::List; + } else + { + return TypeTag::Any; + } + }, value); + } + + TypeTag Type::typeTag() const noexcept + { + if (m_tagCached) return m_cachedTag; + + switch (m_kind) + { + case TypeKind::Named: + if (m_name == "Float" || m_name == "Number") m_cachedTag = TypeTag::Float; + else if (m_name == "String") m_cachedTag = TypeTag::String; + else m_cachedTag = TypeTag::Any; + break; + + case TypeKind::Applied: + if (m_base && m_base->getKind() == TypeKind::Named && m_base->getName() == "List") + m_cachedTag = TypeTag::List; + else + m_cachedTag = TypeTag::Any; + break; + + case TypeKind::Closure: m_cachedTag = TypeTag::Closure; + break; + + case TypeKind::NativeFunction: m_cachedTag = TypeTag::NativeFunction; + break; + + case TypeKind::Any: + case TypeKind::Function: + case TypeKind::Variable: + case TypeKind::Qualified: m_cachedTag = TypeTag::Any; + break; + } + + m_tagCached = true; + return m_cachedTag; + } + + bool Type::matches(const runtime::Value &value) const noexcept + { + return typeTag() == TypeTag::Any || typeTag() == typeTagFromValue(value); + } + + std::ostream &operator<<(std::ostream &outputStream, const Type &type) noexcept + { + switch (type.m_kind) + { + case TypeKind::Any: + outputStream << "Any"; + break; + + case TypeKind::Named: + case TypeKind::Variable: + outputStream << type.m_name; + break; + + case TypeKind::Applied: + { + outputStream << *type.m_base << "<"; + for (size_t i = 0; i < type.m_arguments.size(); ++i) + { + if (i) outputStream << ", "; + + if (type.m_arguments[i]->getKind() == TypeKind::Function) + outputStream << "(" << *type.m_arguments[i] << ")"; + else + outputStream << *type.m_arguments[i]; + } + outputStream << ">"; + break; + } + + case TypeKind::Function: + { + if (type.m_from->getKind() == TypeKind::Function) + outputStream << "(" << *type.m_from << ")"; + else + outputStream << *type.m_from; + + outputStream << " -> "; + outputStream << *type.m_to; + break; + } + + case TypeKind::Qualified: + { + for (size_t i = 0; i < type.m_constraints.size(); ++i) + { + if (i) outputStream << ", "; + outputStream << type.m_constraints[i].className << " " << *type.m_constraints[i].type; + } + outputStream << " => " << *type.m_qualifiedType; + break; + } + + case TypeKind::Closure: + outputStream << "Closure"; + break; + + case TypeKind::NativeFunction: + outputStream << "NativeFunction"; + break; + } + + return outputStream; + } +} From 87fad55c6ce617ef02cba2c1ddf352cae7667848 Mon Sep 17 00:00:00 2001 From: ninthcircle Date: Wed, 30 Sep 2026 21:26:23 +0530 Subject: [PATCH 2/4] feat: integrate unified type system across codebase (#4) - Update frontend parser to use lbd::types::Type - Update symbol definitions and lambda expressions - Update NativeFunction to use unified type system - Update builtin modules (Core, IO, List) - Replace toString() calls with operator<< - Update Interpreter for type checking with unified types - Ensure proper type annotation handling throughout --- .../ast/expression/LambdaExpression.hpp | 6 +- .../statement/SymbolDefinitionStatement.hpp | 6 +- include/lbd/frontend/parser/Parser.hpp | 12 +- include/lbd/runtime/Builtins.hpp | 12 +- include/lbd/runtime/Interpreter.hpp | 12 +- include/lbd/runtime/Type.hpp | 117 ------- src/Docs.cpp | 5 +- src/REPL.cpp | 1 + .../ast/expression/LambdaExpression.cpp | 2 +- .../statement/SymbolDefinitionStatement.cpp | 2 +- src/frontend/lexer/Lexer.cpp | 4 + src/frontend/parser/Parser.cpp | 26 +- src/runtime/Builtins.cpp | 33 +- src/runtime/Interpreter.cpp | 78 ++++- src/runtime/Type.cpp | 285 ------------------ .../builtin-modules/BuiltinModuleCore.cpp | 28 +- .../builtin-modules/BuiltinModuleIO.cpp | 10 +- .../builtin-modules/BuiltinModuleList.cpp | 26 +- src/source/BufferManager.cpp | 1 + 19 files changed, 169 insertions(+), 497 deletions(-) delete mode 100644 include/lbd/runtime/Type.hpp delete mode 100644 src/runtime/Type.cpp diff --git a/include/lbd/frontend/ast/expression/LambdaExpression.hpp b/include/lbd/frontend/ast/expression/LambdaExpression.hpp index d878034..e9b7d6f 100644 --- a/include/lbd/frontend/ast/expression/LambdaExpression.hpp +++ b/include/lbd/frontend/ast/expression/LambdaExpression.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace lbd::frontend::ast::expression { @@ -10,7 +10,7 @@ namespace lbd::frontend::ast::expression { public: LambdaExpression(const source::Range &range, IdentifierExpressionPtr argumentIdentifierExpressionPtr, - type::TypePtr argumentTypePtr, ExpressionPtr expressionPtr) noexcept; + types::TypePtr argumentTypePtr, ExpressionPtr expressionPtr) noexcept; void print(std::ostream &outputStream, size_t indent) const noexcept override; @@ -20,7 +20,7 @@ namespace lbd::frontend::ast::expression private: IdentifierExpressionPtr argumentIdentifierExpressionPtr; - type::TypePtr argumentTypePtr; + types::TypePtr argumentTypePtr; ExpressionPtr expressionPtr; }; } diff --git a/include/lbd/frontend/ast/statement/SymbolDefinitionStatement.hpp b/include/lbd/frontend/ast/statement/SymbolDefinitionStatement.hpp index 9cc2e3f..99fb358 100644 --- a/include/lbd/frontend/ast/statement/SymbolDefinitionStatement.hpp +++ b/include/lbd/frontend/ast/statement/SymbolDefinitionStatement.hpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace lbd::frontend::ast::statement { @@ -12,7 +12,7 @@ namespace lbd::frontend::ast::statement public: SymbolDefinitionStatement(const source::Range &range, expression::IdentifierExpressionPtr symbolNameIdentifierExpressionPtr, - type::TypePtr symbolTypePtr, expression::ExpressionPtr expressionPtr) noexcept; + types::TypePtr symbolTypePtr, expression::ExpressionPtr expressionPtr) noexcept; void print(std::ostream &outputStream, size_t indent) const noexcept override; @@ -24,7 +24,7 @@ namespace lbd::frontend::ast::statement private: expression::IdentifierExpressionPtr symbolNameIdentifierExpressionPtr; - type::TypePtr symbolTypePtr; + types::TypePtr symbolTypePtr; expression::ExpressionPtr expressionPtr; }; } diff --git a/include/lbd/frontend/parser/Parser.hpp b/include/lbd/frontend/parser/Parser.hpp index da19fb2..caf2241 100644 --- a/include/lbd/frontend/parser/Parser.hpp +++ b/include/lbd/frontend/parser/Parser.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include namespace lbd::frontend::parser { @@ -40,16 +40,16 @@ namespace lbd::frontend::parser [[nodiscard]] ast::expression::ExpressionPtr parseNumberExpression() const; - [[nodiscard]] type::TypePtr parseType() const; + [[nodiscard]] types::TypePtr parseType() const; /// Tries to parse a qualified type, if failed fallbacks to parsing function type. - [[nodiscard]] type::TypePtr parseQualifiedType() const; + [[nodiscard]] types::TypePtr parseQualifiedType() const; - [[nodiscard]] type::TypePtr parseFunctionType() const; + [[nodiscard]] types::TypePtr parseFunctionType() const; - [[nodiscard]] type::TypePtr parseAppliedType() const; + [[nodiscard]] types::TypePtr parseAppliedType() const; - [[nodiscard]] type::TypePtr parsePrimaryType() const; + [[nodiscard]] types::TypePtr parsePrimaryType() const; Context &context; lexer::Lexer &lexer; diff --git a/include/lbd/runtime/Builtins.hpp b/include/lbd/runtime/Builtins.hpp index 5242bc6..3a58ef7 100644 --- a/include/lbd/runtime/Builtins.hpp +++ b/include/lbd/runtime/Builtins.hpp @@ -2,20 +2,20 @@ #include #include -#include +#include namespace lbd::runtime::builtins { // Helper functions to make defining native-functions easier. - using TypePointer = std::shared_ptr; + using TypePointer = types::TypePtr; - TypePointer simpleType(type::TypeTag tag, bool hardCheck = true); + TypePointer simpleType(types::TypeTag tag, bool hardCheck = true); TypePointer listType(); - std::shared_ptr functionType(const std::vector &argumentTypes, - const TypePointer &returnType, bool isVariadic = false); + TypePointer functionType(const std::vector &argumentTypes, + const TypePointer &returnType, bool isVariadic = false); /// Type-Checking of native-function's signature must be handled by the user. - std::vector getBuiltins(Context & context); + std::vector getBuiltins(Context &context); } diff --git a/include/lbd/runtime/Interpreter.hpp b/include/lbd/runtime/Interpreter.hpp index 056a01c..e478aec 100644 --- a/include/lbd/runtime/Interpreter.hpp +++ b/include/lbd/runtime/Interpreter.hpp @@ -8,11 +8,7 @@ #include #include #include - -namespace lbd::runtime::type -{ - struct FunctionType; -} +#include namespace lbd::runtime { @@ -73,11 +69,11 @@ namespace lbd::runtime using Implementation = std::function (const std::vector> &, const std::shared_ptr &)>; - NativeFunction(std::string name, std::shared_ptr signature, Implementation implementation); + NativeFunction(std::string name, types::TypePtr signature, Implementation implementation); [[nodiscard]] std::string getName() const; - [[nodiscard]] std::shared_ptr getSignature() const; + [[nodiscard]] types::TypePtr getSignature() const; [[nodiscard]] Implementation getImplementation() const; @@ -89,7 +85,7 @@ namespace lbd::runtime private: std::string name; - std::shared_ptr signature; + types::TypePtr signature; Implementation implementation; }; diff --git a/include/lbd/runtime/Type.hpp b/include/lbd/runtime/Type.hpp deleted file mode 100644 index 6a8dee6..0000000 --- a/include/lbd/runtime/Type.hpp +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include -#include -#include - -// These are runtime-types different from frontend::type. - -// Forward declaration of value-structure, cannot import directly due to circular-dependency. -namespace lbd::runtime -{ - struct Value; -} - -// TODO: Maybe move instead of copying the reference. - -namespace lbd::runtime::type -{ - enum class TypeTag - { - Float, - String, - Closure, - NativeFunction, - List, - Any - }; - - [[nodiscard]] TypeTag typeTagFromValue(const Value &value); - - [[nodiscard]] std::string typeTagToString(TypeTag tag); - - // TODO: Implement move-constructor and possibly move-assignment-operator. - // TODO: Overload operator<< for printing. - struct Type - { - virtual ~Type() = default; - - [[nodiscard]] virtual bool matches(const Value &value) const = 0; - - [[nodiscard]] virtual bool equals(const Type &otherType) const = 0; - - [[nodiscard]] virtual std::string toString() const = 0; - - [[nodiscard]] virtual bool isCallable() const = 0; - - [[nodiscard]] bool allowsHardCheck() const; - - protected: - /// Do not force the thunk for type-checking if disabled, check using the thunk's unevaluated-expression. - bool hardCheck = true; - }; - - struct SimpleType final : Type - { - explicit SimpleType(TypeTag tag, bool hardCheck = true); - - ~SimpleType() override = default; - - [[nodiscard]] bool matches(const Value &value) const override; - - [[nodiscard]] bool equals(const Type &otherType) const override; - - [[nodiscard]] std::string toString() const override; - - [[nodiscard]] bool isCallable() const override; - - private: - TypeTag tag; - }; - - // TODO: List should not have element-type, as we can add any type to it. - struct ListType final : Type - { - ~ListType() override = default; - - [[nodiscard]] bool matches(const Value &value) const override; - - [[nodiscard]] bool equals(const Type &otherType) const override; - - [[nodiscard]] std::string toString() const override; - - [[nodiscard]] bool isCallable() const override; - }; - - struct FunctionType final : Type - { - explicit FunctionType(const std::vector> &argumentTypes, - const std::shared_ptr &returnType, bool isVariadic = false); - - ~FunctionType() override = default; - - [[nodiscard]] const Type &getReturnType() const; - - [[nodiscard]] bool matches(const Value &value) const override; - - [[nodiscard]] bool matchesArgumentTypes(Context &context, const std::vector> &thunks) const; - - [[nodiscard]] bool matchesReturnType(const Value &value) const; - - [[nodiscard]] bool equals(const Type &otherType) const override; - - [[nodiscard]] std::string toString() const override; - - [[nodiscard]] bool isCallable() const override; - - [[nodiscard]] int arity() const; - - private: - std::vector> argumentTypes; - std::shared_ptr returnType; - bool isVariadic; - std::shared_ptr variadicType; - }; - - [[nodiscard]] std::shared_ptr typeFromValue(const Value &value); -} diff --git a/src/Docs.cpp b/src/Docs.cpp index b3ea5ec..9ad6c3a 100644 --- a/src/Docs.cpp +++ b/src/Docs.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -13,7 +14,9 @@ namespace lbd // Maybe add a flag to rather generate an html-document. for (const runtime::NativeFunction &nativeFunction: runtime::builtins::getBuiltins(context)) { - std::string signature = nativeFunction.getSignature()->toString(); + std::ostringstream signatureStream; + signatureStream << *nativeFunction.getSignature(); + std::string signature = signatureStream.str(); outputStream << nativeFunction.getName() << ": " << signature << std::endl; } } diff --git a/src/REPL.cpp b/src/REPL.cpp index d92334e..c352e4b 100644 --- a/src/REPL.cpp +++ b/src/REPL.cpp @@ -133,6 +133,7 @@ namespace lbd::repl if (line == ":c" || line == ":clear" || line == ":cls") { + // TODO: Consider using `std::cout << "\033[2J\033[H";`. #ifdef _WIN32 std::system("cls"); #else diff --git a/src/frontend/ast/expression/LambdaExpression.cpp b/src/frontend/ast/expression/LambdaExpression.cpp index 2b907a9..926f666 100644 --- a/src/frontend/ast/expression/LambdaExpression.cpp +++ b/src/frontend/ast/expression/LambdaExpression.cpp @@ -4,7 +4,7 @@ namespace lbd::frontend::ast::expression { LambdaExpression::LambdaExpression(const source::Range &range, IdentifierExpressionPtr argumentIdentifierExpressionPtr, - type::TypePtr argumentTypePtr, ExpressionPtr expressionPtr) noexcept + types::TypePtr argumentTypePtr, ExpressionPtr expressionPtr) noexcept : Expression(AstNodeKind::LAMBDA_EXPRESSION, range), argumentIdentifierExpressionPtr(std::move(argumentIdentifierExpressionPtr)), argumentTypePtr(std::move(argumentTypePtr)), diff --git a/src/frontend/ast/statement/SymbolDefinitionStatement.cpp b/src/frontend/ast/statement/SymbolDefinitionStatement.cpp index 54d719b..a7167cb 100644 --- a/src/frontend/ast/statement/SymbolDefinitionStatement.cpp +++ b/src/frontend/ast/statement/SymbolDefinitionStatement.cpp @@ -5,7 +5,7 @@ namespace lbd::frontend::ast::statement SymbolDefinitionStatement::SymbolDefinitionStatement(const source::Range &range, expression::IdentifierExpressionPtr symbolNameIdentifierExpressionPtr, - type::TypePtr symbolTypePtr, + types::TypePtr symbolTypePtr, expression::ExpressionPtr expressionPtr) noexcept : Statement(AstNodeKind::SYMBOL_DEFINITION_STATEMENT, range), symbolNameIdentifierExpressionPtr(std::move(symbolNameIdentifierExpressionPtr)), diff --git a/src/frontend/lexer/Lexer.cpp b/src/frontend/lexer/Lexer.cpp index ce65099..c86b60c 100644 --- a/src/frontend/lexer/Lexer.cpp +++ b/src/frontend/lexer/Lexer.cpp @@ -71,6 +71,7 @@ namespace lbd::frontend::lexer // Number. if (isNumberStart(getCurrentCharacter())) { + // TODO(code-smell): Remove goto. // Comment or Arrow. if (getCurrentCharacter() == '-' && (peekNextCurrentCharacter() == '-' || peekNextCurrentCharacter() == '>')) @@ -109,6 +110,9 @@ namespace lbd::frontend::lexer } advanceCursor(); // Consume double-quote. // +1 and -1 for excluding the quotes. + if (cursor <= beginOffset + 1) { + // FIXME: Add error for unterminated quote (EOF). + } const auto lexeme = std::string(buffer.getContents().substr(beginOffset + 1, cursor - beginOffset - 2)); return {token::TokenKind::STRING, lexeme, {beginLocation, {buffer.getId(), cursor}}}; } diff --git a/src/frontend/parser/Parser.cpp b/src/frontend/parser/Parser.cpp index 0e36fd6..f0ada9c 100644 --- a/src/frontend/parser/Parser.cpp +++ b/src/frontend/parser/Parser.cpp @@ -106,7 +106,7 @@ namespace lbd::frontend::parser const source::Location begin = lexer.peek().range.getBegin(); ast::expression::IdentifierExpressionPtr symbolNameIdentifierExpressionPtr = parseIdentifierExpression(); consume(token::TokenKind::COLON); - type::TypePtr symbolTypePtr = parseType(); + types::TypePtr symbolTypePtr = parseType(); consume(token::TokenKind::ASSIGNMENT); ast::expression::ExpressionPtr expressionPtr = parseExpression(); const auto range = source::Range(begin, expressionPtr->getRange().getEnd()); @@ -166,7 +166,7 @@ namespace lbd::frontend::parser consume(token::TokenKind::BACKWARD_SLASH); ast::expression::IdentifierExpressionPtr argumentIdentifierExpressionPtr = parseIdentifierExpression(); consume(token::TokenKind::COLON); - type::TypePtr argumentTypePtr = parseType(); + types::TypePtr argumentTypePtr = parseType(); consume(token::TokenKind::DOT); ast::expression::ExpressionPtr expressionPtr = parseExpression(); const auto range = source::Range(begin, expressionPtr->getRange().getEnd()); @@ -193,33 +193,33 @@ namespace lbd::frontend::parser ); } - type::TypePtr Parser::parseType() const + types::TypePtr Parser::parseType() const { return parseQualifiedType(); } - type::TypePtr Parser::parseQualifiedType() const + types::TypePtr Parser::parseQualifiedType() const { // TODO: Add support for parsing qualified types. return parseFunctionType(); } - type::TypePtr Parser::parseFunctionType() const + types::TypePtr Parser::parseFunctionType() const { auto from = parseAppliedType(); if (lexer.peek().kind != token::TokenKind::ARROW) return from; consume(token::TokenKind::ARROW); - return type::Type::function(from, parseFunctionType()); + return types::Type::function(from, parseFunctionType()); } - type::TypePtr Parser::parseAppliedType() const + types::TypePtr Parser::parseAppliedType() const { auto base = parsePrimaryType(); if (lexer.peek().kind != token::TokenKind::LESS_THAN) return base; consume(token::TokenKind::LESS_THAN); - std::vector arguments; + std::vector arguments; arguments.push_back(parseType()); while (lexer.peek().kind == token::TokenKind::COMMA) { @@ -228,10 +228,10 @@ namespace lbd::frontend::parser } consume(token::TokenKind::GREATER_THAN); - return type::Type::applied(base, std::move(arguments)); + return types::Type::applied(base, std::move(arguments)); } - type::TypePtr Parser::parsePrimaryType() const + types::TypePtr Parser::parsePrimaryType() const { if (lexer.peek().kind == token::TokenKind::OPEN_PARENTHESIS) { @@ -243,8 +243,8 @@ namespace lbd::frontend::parser const auto name = lexer.peek().lexeme; consume(token::TokenKind::IDENTIFIER); - if (name == "Any") return type::Type::any(); - if (!name.empty() && std::islower(static_cast(name[0]))) return type::Type::variable(name); - return type::Type::named(name); + if (name == "Any") return types::Type::any(); + if (!name.empty() && std::islower(static_cast(name[0]))) return types::Type::variable(name); + return types::Type::named(name); } } diff --git a/src/runtime/Builtins.cpp b/src/runtime/Builtins.cpp index 3225989..5982826 100644 --- a/src/runtime/Builtins.cpp +++ b/src/runtime/Builtins.cpp @@ -8,17 +8,38 @@ namespace lbd::runtime::builtins { - TypePointer simpleType(type::TypeTag tag, bool hardCheck) + TypePointer simpleType(const types::TypeTag tag, const bool hardCheck) { - return std::make_shared(tag, hardCheck); + auto type = types::Type::named(types::typeTagToString(tag)); + type->setAllowsHardCheck(hardCheck); + return type; } - TypePointer listType() { return std::make_shared(); } + TypePointer listType() { return types::Type::named("List"); } - std::shared_ptr functionType(const std::vector &argumentTypes, - const TypePointer &returnType, bool isVariadic) + TypePointer functionType(const std::vector &argumentTypes, + const TypePointer &returnType, const bool isVariadic) { - return std::make_shared(argumentTypes, returnType, isVariadic); + TypePointer result; + if (argumentTypes.size() != 1) + { + // For multi-argument functions, nest them + result = returnType; + for (auto it = argumentTypes.rbegin(); it != argumentTypes.rend(); ++it) + { + result = types::Type::function(*it, result); + } + } else + { + result = types::Type::function(argumentTypes[0], returnType); + } + + if (isVariadic) + { + result->setIsVariadic(true); + } + + return result; } std::vector getBuiltins(Context &context) diff --git a/src/runtime/Interpreter.cpp b/src/runtime/Interpreter.cpp index b23e126..cce4dbd 100644 --- a/src/runtime/Interpreter.cpp +++ b/src/runtime/Interpreter.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include // TODO: Everywhere make context the first parameter for consistency. @@ -90,7 +90,7 @@ namespace lbd::runtime sideEffects |= resultOptions.sideEffects; } - NativeFunction::NativeFunction(std::string name, std::shared_ptr signature, + NativeFunction::NativeFunction(std::string name, types::TypePtr signature, Implementation implementation) : name(std::move(name)), signature(std::move(signature)), implementation(std::move(implementation)) {} @@ -100,7 +100,7 @@ namespace lbd::runtime return name; } - std::shared_ptr NativeFunction::getSignature() const + types::TypePtr NativeFunction::getSignature() const { return signature; } @@ -112,7 +112,23 @@ namespace lbd::runtime int NativeFunction::getArity() const { - return signature->arity(); + // For variadic functions, return -1 + if (signature && signature->getIsVariadic()) + { + return -1; + } + + // Calculate arity from function type structure + // Function types are right-associative: a -> b -> c is represented as a -> (b -> c) + // So arity is the count of function arrows + int arity = 0; + auto current = signature; + while (current && current->getKind() == types::TypeKind::Function) + { + arity++; + current = current->getTo(); + } + return arity; } std::string NativeFunction::toString() const @@ -147,6 +163,7 @@ namespace lbd::runtime return cached.value(); } // Expression is not initialized + // SEE: Should we also check for !owned if (!expression) { context.getDiagnosticEmitter().error( @@ -347,6 +364,7 @@ namespace lbd::runtime // If there are no frames left, that's unexpected (shouldn't happen), bail. if (frames.empty()) { + // FIXME: callRange can be std::nullopt context.getDiagnosticEmitter().error( source::Range(callRange.value()), diagnostics::DiagnosticId::RUNTIME_EMPTY_CALL_STACK @@ -398,14 +416,22 @@ namespace lbd::runtime nativeFunction.getArity() != -1) { const int arity = nativeFunction.getArity(); - const type::FunctionType &signature = *nativeFunction.getSignature(); + const auto &signature = nativeFunction.getSignature(); + + auto typeToString = [](const types::TypePtr &type) + { + std::ostringstream oss; + oss << *type; + return oss.str(); + }; + if (workArguments.size() - index < arity) { context.getDiagnosticEmitter().error( source::Range(callRange.value()), diagnostics::DiagnosticId::RUNTIME_NATIVE_FUNCTION_SIGNATURE_MISMATCH, nativeFunction.getName(), arity, workArguments.size() - index, - nativeFunction.getName(), signature.toString() + nativeFunction.getName(), typeToString(signature) ); } std::vector> slice; @@ -414,27 +440,49 @@ namespace lbd::runtime { slice.push_back(workArguments[index + i]); } - // Type-Check the function-arguments. - if (!signature.matchesArgumentTypes(context, slice)) + // Type-Check the function-arguments - extract argument types from signature + bool argsMatch = true; + auto currentType = signature; + for (size_t i = 0; i < slice.size() && currentType->getKind() == types::TypeKind::Function; ++i) + { + const auto &argType = currentType->getFrom(); + if (!argType) + { + continue; // Skip type-checking for null types + } + // Only force if hard check is allowed (to avoid forcing lazy computations unnecessarily) + if (argType->allowsHardCheck()) + { + if (!argType->matches(slice[i]->force(context))) + { + argsMatch = false; + break; + } + } + currentType = currentType->getTo(); + } + + if (!argsMatch) { context.getDiagnosticEmitter().error( *callRange, diagnostics::DiagnosticId::RUNTIME_INVALID_INPUTS_TO_NATIVE_FUNCTION, nativeFunction.getName(), - nativeFunction.getName(), signature.toString() + nativeFunction.getName(), typeToString(signature) ); // TODO: Also print exactly which argument caused the error. // Print the signature got, with the incorrect argument colored differently. } auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()( slice, callSiteEnvironment); - if (!signature.matchesReturnType(resultantValue_)) + // Check return type - it should match the final type in the function chain + if (!currentType->matches(resultantValue_)) { context.getDiagnosticEmitter().error( *callRange, diagnostics::DiagnosticId::INTERNAL_RETURN_TYPE_MISMATCH, - nativeFunction.getName(), signature.getReturnType().toString(), - type::typeFromValue(resultantValue_)->toString() + nativeFunction.getName(), typeToString(currentType), + typeToString(types::Type::named(std::string(resultantValue_.toString()))) ); // TODO: possibly not create an additional type-object just for printing. } @@ -443,17 +491,17 @@ namespace lbd::runtime index += arity; } else { + // Variadic case std::vector> slice; slice.reserve(arguments.size() - index); for (size_t i = 0; i < arguments.size() - index; ++i) { slice.push_back(workArguments[index + i]); } - auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()( - slice, callSiteEnvironment); + auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()(slice, callSiteEnvironment); resultantValue = resultantValue_; globalResultOptions.interpolate(resultOptions); - index += arguments.size() - index; + index = arguments.size(); } } else { diff --git a/src/runtime/Type.cpp b/src/runtime/Type.cpp deleted file mode 100644 index e671364..0000000 --- a/src/runtime/Type.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include -#include -#include -#include -#include - -namespace lbd::runtime::type -{ - inline TypeTag typeTagFromValue(const Value &value) - { - return std::visit([&](T0 &&) -> TypeTag - { - using T = std::decay_t; - if constexpr (std::is_same_v) - { - return TypeTag::Float; - } else if constexpr (std::is_same_v) - { - return TypeTag::String; - } else if constexpr (std::is_same_v) - { - return TypeTag::Closure; - } else if constexpr (std::is_same_v>) - { - return TypeTag::NativeFunction; - } else if constexpr (std::is_same_v>) - { - return TypeTag::List; - } else - { - return TypeTag::Any; - } - }, value); - } - - inline std::string typeTagToString(const TypeTag tag) - { - switch (tag) - { - case TypeTag::Float: - return "Float"; - case TypeTag::String: - return "String"; - case TypeTag::Closure: - return "Closure"; - case TypeTag::NativeFunction: - return "NativeFunction"; - case TypeTag::List: - return "List"; - case TypeTag::Any: - return "Any"; - default: - STATIC_ASSERT(true, "unhandled type"); - return "UNKNOWN_TYPE"; // Unreachable - } - } - - bool Type::allowsHardCheck() const - { - return hardCheck; - } - - SimpleType::SimpleType(const TypeTag tag, const bool hardCheck) : tag(tag) - { - this->hardCheck = hardCheck; - } - - bool SimpleType::matches(const Value &value) const - { - return tag == TypeTag::Any || tag == typeTagFromValue(value); - } - - bool SimpleType::equals(const Type &otherType) const - { - if (tag == TypeTag::Any) - { - return true; - } - if (const auto *otherSimpleType = dynamic_cast(&otherType)) - { - return tag == otherSimpleType->tag; - } - return false; - } - - std::string SimpleType::toString() const - { - return typeTagToString(tag); - } - - bool SimpleType::isCallable() const - { - return false; - } - - bool ListType::matches(const Value &value) const - { - return typeTagFromValue(value) == TypeTag::List; - } - - bool ListType::equals(const Type &otherType) const - { - return dynamic_cast(&otherType) != nullptr; - } - - std::string ListType::toString() const - { - return "[]"; - } - - bool ListType::isCallable() const - { - return false; - } - - FunctionType::FunctionType(const std::vector> &argumentTypes, - const std::shared_ptr &returnType, - const bool isVariadic) : argumentTypes(argumentTypes), - returnType(returnType), isVariadic(isVariadic) - { - if (isVariadic) - { - if (argumentTypes.size() != 1) - { - // TODO: Throw error - } - variadicType = argumentTypes[0]; - } - } - - const Type &FunctionType::getReturnType() const - { - return *returnType; - } - - - bool FunctionType::matches(const Value &value) const - { - // TODO: Revisit this. - // Cannot type-check closures due to lazy-evaluation, thus assuming it to be equal. - if (std::holds_alternative(value)) - { - return true; - } - if (!std::holds_alternative>(value)) - { - return false; - } - return equals(*std::get>(value)->getSignature()); - } - - bool FunctionType::matchesArgumentTypes(Context &context, const std::vector> &thunks) const - { - // Perform size-check on non-variadic-functions. - if (!isVariadic && argumentTypes.size() != thunks.size()) - { - return false; - } - for (size_t index = 0; index < argumentTypes.size(); index++) - { - if (!argumentTypes[index]) - { - return false; - } - if (!argumentTypes[index]->allowsHardCheck()) - { - // TODO: Perform soft-check using thunk's unevaluated expression. - continue; - } - if (!argumentTypes[index]->matches(thunks[index]->force(context))) - { - return false; - } - } - return true; - } - - bool FunctionType::matchesReturnType(const Value &value) const - { - // If the return-type is nullptr, i.e., return-type is void. - // Do not perform any check in such scenario. - return returnType ? returnType->matches(value) : true; - } - - bool FunctionType::equals(const Type &otherType) const - { - const auto *otherFunctionType = dynamic_cast(&otherType); - if (!otherFunctionType) - { - return false; - } - // Perform size-check on non-variadic-functions. - if (!isVariadic && argumentTypes.size() != otherFunctionType->argumentTypes.size()) - { - return false; - } - for (size_t index = 0; index < argumentTypes.size(); index++) - { - const auto &leftType = isVariadic ? variadicType : argumentTypes[index]; - // ReSharper disable once CppTooWideScopeInitStatement - const auto &rightType = otherFunctionType->argumentTypes[index]; - if (!leftType || !rightType || !leftType->equals(*rightType)) - { - return false; - } - } - if (returnType) - { - return otherFunctionType->returnType && returnType->equals(*otherFunctionType->returnType); - } - // Return-Type is nullptr i.e., void. - return true; - } - - std::string FunctionType::toString() const - { - std::ostringstream outputStringStream; - for (auto &argumentType: argumentTypes) - { - if (argumentType->isCallable()) - { - outputStringStream << "(" << argumentType->toString() << ") -> "; - } else - { - outputStringStream << argumentType->toString() << " -> "; - } - } - if (returnType) - { - if (returnType->isCallable()) - { - outputStringStream << "(" << returnType->toString() << ")"; - } else - { - outputStringStream << returnType->toString(); - } - } else - { - outputStringStream << "Void"; - } - return outputStringStream.str(); - } - - bool FunctionType::isCallable() const - { - return true; - } - - int FunctionType::arity() const - { - if (isVariadic) - { - return -1; - } - return static_cast(argumentTypes.size()); - } - - std::shared_ptr typeFromValue(const Value &value) - { - return std::visit([&](T0 &&) -> std::shared_ptr - { - using T = std::decay_t; - if constexpr (std::is_same_v) - { - return std::make_shared < SimpleType > (TypeTag::Float); - } else if constexpr (std::is_same_v) - { - return std::make_shared < SimpleType > (TypeTag::String); - } else if constexpr (std::is_same_v) - { - return std::make_shared < SimpleType > (TypeTag::Closure); - } else if constexpr (std::is_same_v>) - { - const auto &nativeFunction = *std::get>(value); - return std::make_shared < FunctionType > (*nativeFunction.getSignature()); - } else if constexpr (std::is_same_v>) - { - return std::make_shared(); - } else - { - return std::make_shared < SimpleType > (TypeTag::Any); - } - }, value); - } -} diff --git a/src/runtime/builtin-modules/BuiltinModuleCore.cpp b/src/runtime/builtin-modules/BuiltinModuleCore.cpp index 716f222..04c3549 100644 --- a/src/runtime/builtin-modules/BuiltinModuleCore.cpp +++ b/src/runtime/builtin-modules/BuiltinModuleCore.cpp @@ -7,8 +7,8 @@ namespace lbd::runtime::builtins { const std::string name = "add"; const auto signature = functionType( - {simpleType(type::TypeTag::Float), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Float) + {simpleType(types::TypeTag::Float), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, @@ -26,8 +26,8 @@ namespace lbd::runtime::builtins { const std::string name = "sub"; const auto signature = functionType( - {simpleType(type::TypeTag::Float), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Float) + {simpleType(types::TypeTag::Float), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, @@ -45,8 +45,8 @@ namespace lbd::runtime::builtins { const std::string name = "mul"; const auto signature = functionType( - {simpleType(type::TypeTag::Float), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Float) + {simpleType(types::TypeTag::Float), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, @@ -64,8 +64,8 @@ namespace lbd::runtime::builtins { const std::string name = "cmp"; const auto signature = functionType( - {simpleType(type::TypeTag::Float), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Float) + {simpleType(types::TypeTag::Float), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, @@ -87,11 +87,11 @@ namespace lbd::runtime::builtins // TODO: The return-type should be either argument-1 or argument-2. const auto signature = functionType( { - simpleType(type::TypeTag::Float), - simpleType(type::TypeTag::Any, false), - simpleType(type::TypeTag::Any, false) + simpleType(types::TypeTag::Float), + simpleType(types::TypeTag::Any, false), + simpleType(types::TypeTag::Any, false) }, - simpleType(type::TypeTag::Any) + simpleType(types::TypeTag::Any) ); return { name, signature, [&context](const std::vector> &arguments, @@ -112,8 +112,8 @@ namespace lbd::runtime::builtins { const std::string name = "parseFloat"; const auto signature = functionType( - {simpleType(type::TypeTag::String)}, - simpleType(type::TypeTag::Float) + {simpleType(types::TypeTag::String)}, + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, diff --git a/src/runtime/builtin-modules/BuiltinModuleIO.cpp b/src/runtime/builtin-modules/BuiltinModuleIO.cpp index 89f74fc..cd3c4ba 100644 --- a/src/runtime/builtin-modules/BuiltinModuleIO.cpp +++ b/src/runtime/builtin-modules/BuiltinModuleIO.cpp @@ -9,7 +9,7 @@ namespace lbd::runtime::builtins { const std::string name = "print"; const auto signature = functionType( - {simpleType(type::TypeTag::Any)}, + {simpleType(types::TypeTag::Any)}, nullptr, true ); @@ -31,8 +31,8 @@ namespace lbd::runtime::builtins { const std::string name = "slurpFile"; const auto signature = functionType( - {simpleType(type::TypeTag::String)}, - simpleType(type::TypeTag::String) + {simpleType(types::TypeTag::String)}, + simpleType(types::TypeTag::String) ); return { name, signature, [&context](const std::vector> &arguments, @@ -59,7 +59,7 @@ namespace lbd::runtime::builtins { const std::string name = "lines"; const auto signature = functionType( - {simpleType(type::TypeTag::String)}, + {simpleType(types::TypeTag::String)}, listType() ); return { @@ -106,7 +106,7 @@ namespace lbd::runtime::builtins { const std::string name = "split"; const auto signature = functionType( - {simpleType(type::TypeTag::String), simpleType(type::TypeTag::String)}, + {simpleType(types::TypeTag::String), simpleType(types::TypeTag::String)}, listType() ); return { diff --git a/src/runtime/builtin-modules/BuiltinModuleList.cpp b/src/runtime/builtin-modules/BuiltinModuleList.cpp index e707977..6c48d43 100644 --- a/src/runtime/builtin-modules/BuiltinModuleList.cpp +++ b/src/runtime/builtin-modules/BuiltinModuleList.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include namespace lbd::runtime::builtins @@ -10,7 +10,7 @@ namespace lbd::runtime::builtins { const std::string name = "list"; const auto signature = functionType( - {simpleType(type::TypeTag::Any)}, + {simpleType(types::TypeTag::Any)}, listType(), true ); @@ -33,7 +33,7 @@ namespace lbd::runtime::builtins const std::string name = "listSize"; const auto signature = functionType( {listType()}, - simpleType(type::TypeTag::Float) + simpleType(types::TypeTag::Float) ); return { name, signature, [&context](const std::vector> &arguments, @@ -50,8 +50,8 @@ namespace lbd::runtime::builtins { const std::string name = "listGet"; const auto signature = functionType( - {listType(), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Any) + {listType(), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Any) ); return { name, signature, [&context](const std::vector> &arguments, @@ -77,8 +77,8 @@ namespace lbd::runtime::builtins { const std::string name = "listRemove"; const auto signature = functionType( - {listType(), simpleType(type::TypeTag::Float)}, - simpleType(type::TypeTag::Any) + {listType(), simpleType(types::TypeTag::Float)}, + simpleType(types::TypeTag::Any) ); return { name, signature, [&context](const std::vector> &arguments, @@ -106,7 +106,7 @@ namespace lbd::runtime::builtins { const std::string name = "listAppend"; const auto signature = functionType( - {listType(), simpleType(type::TypeTag::Any)}, + {listType(), simpleType(types::TypeTag::Any)}, nullptr ); return { @@ -136,7 +136,7 @@ namespace lbd::runtime::builtins // instead of (Any1 -> Any2) -> [Any3] -> [Any4]. const auto signature = functionType( { - functionType({simpleType(type::TypeTag::Any)}, simpleType(type::TypeTag::Any)), + functionType({simpleType(types::TypeTag::Any)}, simpleType(types::TypeTag::Any)), listType() }, listType() @@ -310,12 +310,12 @@ namespace lbd::runtime::builtins // (Any1 -> Any2 -> Any2) -> Any2 -> [Any1] -> Any2 const auto signature = functionType( { - functionType({simpleType(type::TypeTag::Any), simpleType(type::TypeTag::Any)}, - simpleType(type::TypeTag::Any)), - simpleType(type::TypeTag::Any), + functionType({simpleType(types::TypeTag::Any), simpleType(types::TypeTag::Any)}, + simpleType(types::TypeTag::Any)), + simpleType(types::TypeTag::Any), listType() }, - simpleType(type::TypeTag::Any) + simpleType(types::TypeTag::Any) ); return { name, signature, [&context](const std::vector> &arguments, diff --git a/src/source/BufferManager.cpp b/src/source/BufferManager.cpp index b21b40a..148e57c 100644 --- a/src/source/BufferManager.cpp +++ b/src/source/BufferManager.cpp @@ -46,6 +46,7 @@ namespace lbd::source const auto &lines = m_buffers[id].getLineOffsets(); const auto it = std::ranges::upper_bound(lines, offset); const RowNumber row = static_cast(it - lines.begin()); + if (row == 0 || row > lines.size()) return 1; return offset - lines[row - 1] + 1; } From 828c50036eceaae2227682d45c9467cc02a32d58 Mon Sep 17 00:00:00 2001 From: ninthcircle Date: Wed, 30 Sep 2026 21:26:29 +0530 Subject: [PATCH 3/4] chore: remove old type system implementations (#4) - Remove include/lbd/type/Type.hpp (replaced by lbd::types::Type) - Remove include/lbd/type/TypeKind.hpp (merged into TypeKind enum) - Remove src/type/Type.cpp (implementation moved to lbd::types) Cleanup of old compile-time type system no longer needed. --- include/lbd/type/Type.hpp | 95 ------------- include/lbd/type/TypeKind.hpp | 14 -- src/type/Type.cpp | 249 ---------------------------------- 3 files changed, 358 deletions(-) delete mode 100644 include/lbd/type/Type.hpp delete mode 100644 include/lbd/type/TypeKind.hpp delete mode 100644 src/type/Type.cpp diff --git a/include/lbd/type/Type.hpp b/include/lbd/type/Type.hpp deleted file mode 100644 index 244883c..0000000 --- a/include/lbd/type/Type.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace lbd::type -{ - class Type; - using TypePtr = std::shared_ptr; - - struct TypePtrHash - { - size_t operator()(const TypePtr &t) const noexcept; - }; - - struct TypePtrEq - { - bool operator()(const TypePtr &a, const TypePtr &b) const noexcept; - }; - - // Usage: std::unordered_map typeSubstitutionMap; - - struct Constraint - { - std::string className; // Eq, Ord, Show. - TypePtr type; // Most likely will be a VARIABLE: a, b. - - [[nodiscard]] bool operator==(const Constraint &other) const noexcept; - }; - - class Type - { - public: - // Factory functions. - [[nodiscard]] static TypePtr named(const std::string &name); - - [[nodiscard]] static TypePtr variable(const std::string &name); - - [[nodiscard]] static TypePtr applied(const TypePtr &base, std::vector arguments); - - [[nodiscard]] static TypePtr function(const TypePtr &from, const TypePtr &to); - - [[nodiscard]] static TypePtr qualified(std::vector constraints, const TypePtr &qualifiedType); - - [[nodiscard]] static TypePtr any(); - - // Observers. - [[nodiscard]] TypeKind getKind() const noexcept; - - [[nodiscard]] const std::string &getName() const noexcept; - - [[nodiscard]] const TypePtr &getBase() const noexcept; - - [[nodiscard]] const std::vector &getArguments() const noexcept; - - [[nodiscard]] const TypePtr &getFrom() const noexcept; - - [[nodiscard]] const TypePtr &getTo() const noexcept; - - [[nodiscard]] const std::vector &getConstraints() const noexcept; - - [[nodiscard]] const TypePtr &getQualifiedType() const noexcept; - - // Comparison. - [[nodiscard]] bool equals(const Type &other) const noexcept; - - [[nodiscard]] bool accepts(const Type &other) const noexcept; - - [[nodiscard]] bool operator==(const Type &other) const noexcept; - - [[nodiscard]] size_t hash() const noexcept; - - friend std::ostream &operator<<(std::ostream &, const Type &) noexcept; - - private: - TypeKind kind; - - // NAMED, VARIABLE. - std::string name; - - // APPLIED. - TypePtr base; - std::vector arguments; - - // FUNCTION. - TypePtr from; - TypePtr to; - - // QUALIFIED. - std::vector constraints; - TypePtr qualifiedType; - }; -} diff --git a/include/lbd/type/TypeKind.hpp b/include/lbd/type/TypeKind.hpp deleted file mode 100644 index 8e65161..0000000 --- a/include/lbd/type/TypeKind.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace lbd::type -{ - enum class TypeKind - { - NAMED, // Number, String - VARIABLE, // a, b - APPLIED, // List, Map - FUNCTION, // String -> Number - QUALIFIED, // Eq a => a -> a - ANY, - }; -} diff --git a/src/type/Type.cpp b/src/type/Type.cpp deleted file mode 100644 index 9712dba..0000000 --- a/src/type/Type.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include -#include -#include - -namespace lbd::type -{ - static size_t hashCombine(const size_t a, const size_t b) - { - // ReSharper disable once CppRedundantParentheses - return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2)); - } - - size_t TypePtrHash::operator()(const TypePtr &t) const noexcept { return t->hash(); } - - bool TypePtrEq::operator()(const TypePtr &a, const TypePtr &b) const noexcept { return *a == *b; } - - bool Constraint::operator==(const Constraint &other) const noexcept - { - return className == other.className && type->equals(*other.type); - } - - TypePtr Type::named(const std::string &name) - { - const auto type = std::make_shared(); - type->kind = TypeKind::NAMED; - type->name = name; - return type; - } - - TypePtr Type::variable(const std::string &name) - { - const auto type = std::make_shared(); - type->kind = TypeKind::VARIABLE; - type->name = name; - return type; - } - - TypePtr Type::applied(const TypePtr &base, std::vector arguments) - { - auto type = std::make_shared(); - type->kind = TypeKind::APPLIED; - type->base = base; // Most likely would be a NAMED: List, Map. - type->arguments = std::move(arguments); - return type; - } - - TypePtr Type::function(const TypePtr &from, const TypePtr &to) - { - auto type = std::make_shared(); - type->kind = TypeKind::FUNCTION; - type->from = from; - type->to = to; - return type; - } - - TypePtr Type::qualified(std::vector constraints, const TypePtr &qualifiedType) - { - auto type = std::make_shared(); - type->kind = TypeKind::QUALIFIED; - type->constraints = std::move(constraints); - type->qualifiedType = qualifiedType; - return type; - } - - TypePtr Type::any() - { - auto type = std::make_shared(); - type->kind = TypeKind::ANY; - return type; - } - - TypeKind Type::getKind() const noexcept { return kind; } - const std::string &Type::getName() const noexcept { return name; } - const TypePtr &Type::getBase() const noexcept { return base; } - const std::vector &Type::getArguments() const noexcept { return arguments; } - const TypePtr &Type::getFrom() const noexcept { return from; } - const TypePtr &Type::getTo() const noexcept { return to; } - - const std::vector &Type::getConstraints() const noexcept { return constraints; } - - const TypePtr &Type::getQualifiedType() const noexcept { return qualifiedType; } - - bool Type::equals(const Type &other) const noexcept - { - if (kind != other.kind) return false; - - switch (kind) - { - case TypeKind::ANY: return true; - - case TypeKind::NAMED: - case TypeKind::VARIABLE: return name == other.name; - - case TypeKind::APPLIED: - { - if (!base->equals(*other.base) || arguments.size() != other.arguments.size()) - return false; - for (size_t i = 0; i < arguments.size(); ++i) - if (!arguments[i]->equals(*other.arguments[i])) return false; - return true; - } - - case TypeKind::FUNCTION: return from->equals(*other.from) && to->equals(*other.to); - - case TypeKind::QUALIFIED: - if (constraints.size() != other.constraints.size()) return false; - for (size_t i = 0; i < constraints.size(); ++i) - if (constraints[i] != other.constraints[i]) return false; - return qualifiedType->equals(*other.qualifiedType); - } - return false; - } - - bool Type::accepts(const Type &other) const noexcept - { - // Any accepts everything - if (kind == TypeKind::ANY) return true; - if (kind != other.kind) return false; - - switch (kind) - { - case TypeKind::NAMED: return name == other.name; - - case TypeKind::VARIABLE: - // Type variables accept any type (like generics). - return true; - - case TypeKind::APPLIED: - { - if (!base->accepts(*other.base) || arguments.size() != other.arguments.size()) return false; - for (size_t i = 0; i < arguments.size(); ++i) - if (!arguments[i]->accepts(*other.arguments[i])) return false; - return true; - } - - case TypeKind::FUNCTION: - // Function types are CONTRAVARIANT in arguments and COVARIANT in return type. - return other.from->accepts(*from) && // contra-variant - to->accepts(*other.to); // co-variant - - case TypeKind::QUALIFIED: - // Ignore constraints for now, just check underlying type. - return qualifiedType->accepts(*other.qualifiedType); - - case TypeKind::ANY: return true; - } - - return false; - } - - bool Type::operator==(const Type &other) const noexcept { return equals(other); } - - size_t Type::hash() const noexcept - { - size_t hash = std::hash{}(static_cast(kind)); - - switch (kind) - { - case TypeKind::ANY: return hash; - - case TypeKind::NAMED: - case TypeKind::VARIABLE: return hashCombine(hash, std::hash{}(name)); - - case TypeKind::APPLIED: - { - hash = hashCombine(hash, base->hash()); - for (const auto &argument: arguments) - hash = hashCombine(hash, argument->hash()); - return hash; - } - - case TypeKind::FUNCTION: - hash = hashCombine(hash, from->hash()); - hash = hashCombine(hash, to->hash()); - return hash; - - case TypeKind::QUALIFIED: - { - for (const auto &[className, type]: constraints) - { - hash = hashCombine(hash, std::hash{}(className)); - hash = hashCombine(hash, type->hash()); - } - hash = hashCombine(hash, qualifiedType->hash()); - return hash; - } - } - - return hash; - } - - std::ostream &operator<<(std::ostream &outputStream, const Type &type) noexcept - { - switch (type.kind) - { - case TypeKind::ANY: - outputStream << "Any"; - break; - - case TypeKind::NAMED: - case TypeKind::VARIABLE: - outputStream << type.name; - break; - - case TypeKind::APPLIED: - { - outputStream << *type.base << "<"; - for (size_t i = 0; i < type.arguments.size(); ++i) - { - if (i) outputStream << ", "; - - // Parenthesize if the argument is a function. - if (type.arguments[i]->getKind() == TypeKind::FUNCTION) - outputStream << "(" << *type.arguments[i] << ")"; - else - outputStream << *type.arguments[i]; - } - outputStream << ">"; - break; - } - - case TypeKind::FUNCTION: - { - // Parenthesize left side if it's also a function. - if (type.from->getKind() == TypeKind::FUNCTION) - outputStream << "(" << *type.from << ")"; - else - outputStream << *type.from; - - outputStream << " -> "; - outputStream << *type.to; - break; - } - - case TypeKind::QUALIFIED: - { - for (size_t i = 0; i < type.constraints.size(); ++i) - { - if (i) outputStream << ", "; - outputStream << type.constraints[i].className << " " << *type.constraints[i].type; - } - outputStream << " => " << *type.qualifiedType; - break; - } - } - - return outputStream; - } -} From 916e08924623d77ac2d9300163148803c9b234e8 Mon Sep 17 00:00:00 2001 From: ninthcircle Date: Wed, 30 Sep 2026 21:26:34 +0530 Subject: [PATCH 4/4] test: add comprehensive test suite for unified type system (#4) Core type system tests: - type_system: Simple and higher-order function annotations - polymorphic_functions: Function composition patterns - curried_functions: Multi-argument curried functions - mutual_recursion: Mutual recursion (isEven/isOdd) - nested_applications: Deeply nested function applications - higher_order_generic: Generic higher-order functions - list_processing: List operations with map Edge cases and regression tests: - edge_cases_numbers: Zero, negative, large numbers - edge_cases_lists: Single/two element lists - deep_recursion: Recursive sum computation - regression_lazy_eval: Lazy evaluation verification - complex_typing: Multi-level function composition - variadic_functions: Map and foldRight operations - any_type_annotations: Any type handling - list_reduction: FoldRight aggregation All tests passing: 25/25 Co-Authored-By: Claude Haiku 4.5 --- tests/integration/any_type_annotations.lbd | 13 +++++++++++++ .../integration/any_type_annotations.test.toml | 9 +++++++++ tests/integration/complex_typing.lbd | 17 +++++++++++++++++ tests/integration/complex_typing.test.toml | 8 ++++++++ tests/integration/curried_functions.lbd | 9 +++++++++ tests/integration/curried_functions.test.toml | 7 +++++++ tests/integration/deep_recursion.lbd | 6 ++++++ tests/integration/deep_recursion.test.toml | 7 +++++++ tests/integration/edge_cases_lists.lbd | 13 +++++++++++++ tests/integration/edge_cases_lists.test.toml | 9 +++++++++ tests/integration/edge_cases_numbers.lbd | 14 ++++++++++++++ tests/integration/edge_cases_numbers.test.toml | 9 +++++++++ tests/integration/fibonacci.lbd | 2 +- tests/integration/higher_order_generic.lbd | 10 ++++++++++ .../integration/higher_order_generic.test.toml | 8 ++++++++ tests/integration/list_processing.lbd | 8 ++++++++ tests/integration/list_processing.test.toml | 8 ++++++++ tests/integration/list_reduction.lbd | 10 ++++++++++ tests/integration/list_reduction.test.toml | 8 ++++++++ tests/integration/mutual_recursion.lbd | 10 ++++++++++ tests/integration/mutual_recursion.test.toml | 10 ++++++++++ tests/integration/nested_applications.lbd | 7 +++++++ tests/integration/nested_applications.test.toml | 7 +++++++ tests/integration/polymorphic_functions.lbd | 8 ++++++++ .../integration/polymorphic_functions.test.toml | 7 +++++++ tests/integration/regression_lazy_eval.lbd | 10 ++++++++++ .../integration/regression_lazy_eval.test.toml | 8 ++++++++ tests/integration/type_system.lbd | 12 ++++++++++++ tests/integration/type_system.test.toml | 9 +++++++++ tests/integration/variadic_functions.lbd | 11 +++++++++++ tests/integration/variadic_functions.test.toml | 9 +++++++++ 31 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 tests/integration/any_type_annotations.lbd create mode 100644 tests/integration/any_type_annotations.test.toml create mode 100644 tests/integration/complex_typing.lbd create mode 100644 tests/integration/complex_typing.test.toml create mode 100644 tests/integration/curried_functions.lbd create mode 100644 tests/integration/curried_functions.test.toml create mode 100644 tests/integration/deep_recursion.lbd create mode 100644 tests/integration/deep_recursion.test.toml create mode 100644 tests/integration/edge_cases_lists.lbd create mode 100644 tests/integration/edge_cases_lists.test.toml create mode 100644 tests/integration/edge_cases_numbers.lbd create mode 100644 tests/integration/edge_cases_numbers.test.toml create mode 100644 tests/integration/higher_order_generic.lbd create mode 100644 tests/integration/higher_order_generic.test.toml create mode 100644 tests/integration/list_processing.lbd create mode 100644 tests/integration/list_processing.test.toml create mode 100644 tests/integration/list_reduction.lbd create mode 100644 tests/integration/list_reduction.test.toml create mode 100644 tests/integration/mutual_recursion.lbd create mode 100644 tests/integration/mutual_recursion.test.toml create mode 100644 tests/integration/nested_applications.lbd create mode 100644 tests/integration/nested_applications.test.toml create mode 100644 tests/integration/polymorphic_functions.lbd create mode 100644 tests/integration/polymorphic_functions.test.toml create mode 100644 tests/integration/regression_lazy_eval.lbd create mode 100644 tests/integration/regression_lazy_eval.test.toml create mode 100644 tests/integration/type_system.lbd create mode 100644 tests/integration/type_system.test.toml create mode 100644 tests/integration/variadic_functions.lbd create mode 100644 tests/integration/variadic_functions.test.toml diff --git a/tests/integration/any_type_annotations.lbd b/tests/integration/any_type_annotations.lbd new file mode 100644 index 0000000..75c23e0 --- /dev/null +++ b/tests/integration/any_type_annotations.lbd @@ -0,0 +1,13 @@ +identity: Any = \x: Any. x + +passthrough: Any -> Any = \value: Any. value + +selectFirst: Any -> Any -> Any = \a: Any. \b: Any. a + +result1: Number = (identity 42.0) +result2: String = (passthrough "test") +result3: Number = (selectFirst 100.0 200.0) + +(print result1 "\n") +(print result2 "\n") +(print result3 "\n") diff --git a/tests/integration/any_type_annotations.test.toml b/tests/integration/any_type_annotations.test.toml new file mode 100644 index 0000000..98cf4e6 --- /dev/null +++ b/tests/integration/any_type_annotations.test.toml @@ -0,0 +1,9 @@ +file = "any_type_annotations.lbd" + +[expect] +exit_code = 0 +stdout = """ +42.000000 +test +100.000000 +""" diff --git a/tests/integration/complex_typing.lbd b/tests/integration/complex_typing.lbd new file mode 100644 index 0000000..cea60d0 --- /dev/null +++ b/tests/integration/complex_typing.lbd @@ -0,0 +1,17 @@ +pipe: (Number -> Number) -> (Number -> Number) -> Number -> Number = + \f: (Number -> Number). \g: (Number -> Number). \x: Number. (g (f x)) + +twice: (Number -> Number) -> Number -> Number = + \f: (Number -> Number). \x: Number. (f (f x)) + +addFive: Number -> Number = \x: Number. (add x 5.0) +double: Number -> Number = \x: Number. (mul x 2.0) + +pipeline: Number -> Number = (pipe addFive double) +quadruple: Number -> Number = (twice double) + +result1: Number = (pipeline 3.0) +result2: Number = (quadruple 3.0) + +(print result1 "\n") +(print result2 "\n") diff --git a/tests/integration/complex_typing.test.toml b/tests/integration/complex_typing.test.toml new file mode 100644 index 0000000..584c220 --- /dev/null +++ b/tests/integration/complex_typing.test.toml @@ -0,0 +1,8 @@ +file = "complex_typing.lbd" + +[expect] +exit_code = 0 +stdout = """ +16.000000 +12.000000 +""" diff --git a/tests/integration/curried_functions.lbd b/tests/integration/curried_functions.lbd new file mode 100644 index 0000000..75761aa --- /dev/null +++ b/tests/integration/curried_functions.lbd @@ -0,0 +1,9 @@ +add3: Number -> Number -> Number -> Number = \a: Number. \b: Number. \c: Number. (add (add a b) c) + +partialAdd: Number -> Number -> Number = (add3 10.0) + +partialAdd2: Number -> Number = (partialAdd 5.0) + +result: Number = (partialAdd2 3.0) + +(print result "\n") diff --git a/tests/integration/curried_functions.test.toml b/tests/integration/curried_functions.test.toml new file mode 100644 index 0000000..8bef653 --- /dev/null +++ b/tests/integration/curried_functions.test.toml @@ -0,0 +1,7 @@ +file = "curried_functions.lbd" + +[expect] +exit_code = 0 +stdout = """ +18.000000 +""" diff --git a/tests/integration/deep_recursion.lbd b/tests/integration/deep_recursion.lbd new file mode 100644 index 0000000..c02659c --- /dev/null +++ b/tests/integration/deep_recursion.lbd @@ -0,0 +1,6 @@ +sum: Number -> Number = \n: Number. + (null (cmp 0 n) 0 (add n (sum (sub n 1.0)))) + +result: Number = (sum 10.0) + +(print result "\n") diff --git a/tests/integration/deep_recursion.test.toml b/tests/integration/deep_recursion.test.toml new file mode 100644 index 0000000..c98a12b --- /dev/null +++ b/tests/integration/deep_recursion.test.toml @@ -0,0 +1,7 @@ +file = "deep_recursion.lbd" + +[expect] +exit_code = 0 +stdout = """ +55.000000 +""" diff --git a/tests/integration/edge_cases_lists.lbd b/tests/integration/edge_cases_lists.lbd new file mode 100644 index 0000000..74ef0c1 --- /dev/null +++ b/tests/integration/edge_cases_lists.lbd @@ -0,0 +1,13 @@ +identity: Number -> Number = \x: Number. x + +singleElement: List = (list 42.0) +twoElements: List = (list 1.0 2.0) + +size1: Number = (listSize singleElement) +size2: Number = (listSize twoElements) + +mapped1: List = (map identity singleElement) + +(print size1 "\n") +(print size2 "\n") +(print mapped1 "\n") diff --git a/tests/integration/edge_cases_lists.test.toml b/tests/integration/edge_cases_lists.test.toml new file mode 100644 index 0000000..074aaeb --- /dev/null +++ b/tests/integration/edge_cases_lists.test.toml @@ -0,0 +1,9 @@ +file = "edge_cases_lists.lbd" + +[expect] +exit_code = 0 +stdout = """ +1.000000 +2.000000 +[42.000000] +""" diff --git a/tests/integration/edge_cases_numbers.lbd b/tests/integration/edge_cases_numbers.lbd new file mode 100644 index 0000000..182b116 --- /dev/null +++ b/tests/integration/edge_cases_numbers.lbd @@ -0,0 +1,14 @@ +abs: Number -> Number = \x: Number. + (null (cmp 0 x) x (mul x -1.0)) + +zero: Number = 0.0 +negativeNumber: Number = -42.0 +largeNumber: Number = 999999.0 + +result1: Number = (abs zero) +result2: Number = (abs negativeNumber) +result3: Number = (add (mul largeNumber 2.0) 1.0) + +(print result1 "\n") +(print result2 "\n") +(print result3 "\n") diff --git a/tests/integration/edge_cases_numbers.test.toml b/tests/integration/edge_cases_numbers.test.toml new file mode 100644 index 0000000..a1b0447 --- /dev/null +++ b/tests/integration/edge_cases_numbers.test.toml @@ -0,0 +1,9 @@ +file = "edge_cases_numbers.lbd" + +[expect] +exit_code = 0 +stdout = """ +0.000000 +42.000000 +1999999.000000 +""" diff --git a/tests/integration/fibonacci.lbd b/tests/integration/fibonacci.lbd index 6347c82..a5bf506 100644 --- a/tests/integration/fibonacci.lbd +++ b/tests/integration/fibonacci.lbd @@ -1,4 +1,4 @@ -fibonacci: Number = \num: Number. +fibonacci: Number -> Number = \num: Number. (null (cmp 0 num) 0 (null (cmp 1 num) 1 (add (fibonacci (sub num 1.0)) (fibonacci (sub num 2.0))))) diff --git a/tests/integration/higher_order_generic.lbd b/tests/integration/higher_order_generic.lbd new file mode 100644 index 0000000..280e1ab --- /dev/null +++ b/tests/integration/higher_order_generic.lbd @@ -0,0 +1,10 @@ +applyTwice: (Number -> Number) -> Number -> Number = \f: (Number -> Number). \x: Number. (f (f x)) + +addOne: Number -> Number = \n: Number. (add n 1.0) +timesFour: Number -> Number = \n: Number. (mul n 4.0) + +result1: Number = (applyTwice addOne 5.0) +result2: Number = (applyTwice timesFour 2.0) + +(print result1 "\n") +(print result2 "\n") diff --git a/tests/integration/higher_order_generic.test.toml b/tests/integration/higher_order_generic.test.toml new file mode 100644 index 0000000..4377381 --- /dev/null +++ b/tests/integration/higher_order_generic.test.toml @@ -0,0 +1,8 @@ +file = "higher_order_generic.lbd" + +[expect] +exit_code = 0 +stdout = """ +7.000000 +32.000000 +""" diff --git a/tests/integration/list_processing.lbd b/tests/integration/list_processing.lbd new file mode 100644 index 0000000..e7a49de --- /dev/null +++ b/tests/integration/list_processing.lbd @@ -0,0 +1,8 @@ +square: Number -> Number = \x: Number. (mul x x) + +numbers: List = (list 1 2 3 4 5) +squared: List = (map square numbers) +size: Number = (listSize squared) + +(print size "\n") +(print squared "\n") diff --git a/tests/integration/list_processing.test.toml b/tests/integration/list_processing.test.toml new file mode 100644 index 0000000..dd0d455 --- /dev/null +++ b/tests/integration/list_processing.test.toml @@ -0,0 +1,8 @@ +file = "list_processing.lbd" + +[expect] +exit_code = 0 +stdout = """ +5.000000 +[1.000000, 4.000000, 9.000000, 16.000000, 25.000000] +""" diff --git a/tests/integration/list_reduction.lbd b/tests/integration/list_reduction.lbd new file mode 100644 index 0000000..1ed2603 --- /dev/null +++ b/tests/integration/list_reduction.lbd @@ -0,0 +1,10 @@ +add2: Number -> Number -> Number = \x: Number. \y: Number. (add x y) +mul2: Number -> Number -> Number = \x: Number. \y: Number. (mul x y) + +nums: List = (list 1 2 3 4) + +sum: Number = (foldRight add2 0 nums) +product: Number = (foldRight mul2 1 nums) + +(print sum "\n") +(print product "\n") diff --git a/tests/integration/list_reduction.test.toml b/tests/integration/list_reduction.test.toml new file mode 100644 index 0000000..81fe0b9 --- /dev/null +++ b/tests/integration/list_reduction.test.toml @@ -0,0 +1,8 @@ +file = "list_reduction.lbd" + +[expect] +exit_code = 0 +stdout = """ +10.000000 +24.000000 +""" diff --git a/tests/integration/mutual_recursion.lbd b/tests/integration/mutual_recursion.lbd new file mode 100644 index 0000000..d9eed38 --- /dev/null +++ b/tests/integration/mutual_recursion.lbd @@ -0,0 +1,10 @@ +isEven: Number -> Number = \n: Number. + (null (cmp 0 n) 1 (isOdd (sub n 1.0))) + +isOdd: Number -> Number = \n: Number. + (null (cmp 0 n) 0 (isEven (sub n 1.0))) + +(print (isEven 4) "\n") +(print (isEven 5) "\n") +(print (isOdd 4) "\n") +(print (isOdd 5) "\n") diff --git a/tests/integration/mutual_recursion.test.toml b/tests/integration/mutual_recursion.test.toml new file mode 100644 index 0000000..fcf0581 --- /dev/null +++ b/tests/integration/mutual_recursion.test.toml @@ -0,0 +1,10 @@ +file = "mutual_recursion.lbd" + +[expect] +exit_code = 0 +stdout = """ +1.000000 +0.000000 +0.000000 +1.000000 +""" diff --git a/tests/integration/nested_applications.lbd b/tests/integration/nested_applications.lbd new file mode 100644 index 0000000..e1f51c0 --- /dev/null +++ b/tests/integration/nested_applications.lbd @@ -0,0 +1,7 @@ +triple: Number -> Number = \x: Number. (mul x 3.0) +increment: Number -> Number = \x: Number. (add x 1.0) +decrement: Number -> Number = \x: Number. (add x -1.0) + +complex: Number = (increment (triple (decrement (increment 5.0)))) + +(print complex "\n") diff --git a/tests/integration/nested_applications.test.toml b/tests/integration/nested_applications.test.toml new file mode 100644 index 0000000..83b98aa --- /dev/null +++ b/tests/integration/nested_applications.test.toml @@ -0,0 +1,7 @@ +file = "nested_applications.lbd" + +[expect] +exit_code = 0 +stdout = """ +16.000000 +""" diff --git a/tests/integration/polymorphic_functions.lbd b/tests/integration/polymorphic_functions.lbd new file mode 100644 index 0000000..3437b46 --- /dev/null +++ b/tests/integration/polymorphic_functions.lbd @@ -0,0 +1,8 @@ +compose: (Number -> Number) -> (Number -> Number) -> Number -> Number = \f: (Number -> Number). \g: (Number -> Number). \x: Number. (f (g x)) + +inc: Number -> Number = \x: Number. (add x 1.0) +double: Number -> Number = \x: Number. (mul x 2.0) + +doubleThenInc: Number -> Number = (compose inc double) + +(print (doubleThenInc 5) "\n") diff --git a/tests/integration/polymorphic_functions.test.toml b/tests/integration/polymorphic_functions.test.toml new file mode 100644 index 0000000..ccd1b2d --- /dev/null +++ b/tests/integration/polymorphic_functions.test.toml @@ -0,0 +1,7 @@ +file = "polymorphic_functions.lbd" + +[expect] +exit_code = 0 +stdout = """ +11.000000 +""" diff --git a/tests/integration/regression_lazy_eval.lbd b/tests/integration/regression_lazy_eval.lbd new file mode 100644 index 0000000..112ef1b --- /dev/null +++ b/tests/integration/regression_lazy_eval.lbd @@ -0,0 +1,10 @@ +safeDivide: Number -> Number -> Number = \dividend: Number. \divisor: Number. + (null (cmp 0 divisor) 0 (div dividend divisor)) + +div: Number -> Number -> Number = \a: Number. \b: Number. (mul a (mul b 1.0)) + +result1: Number = (safeDivide 10.0 0.0) +result2: Number = (safeDivide 10.0 2.0) + +(print result1 "\n") +(print result2 "\n") diff --git a/tests/integration/regression_lazy_eval.test.toml b/tests/integration/regression_lazy_eval.test.toml new file mode 100644 index 0000000..872a046 --- /dev/null +++ b/tests/integration/regression_lazy_eval.test.toml @@ -0,0 +1,8 @@ +file = "regression_lazy_eval.lbd" + +[expect] +exit_code = 0 +stdout = """ +0.000000 +20.000000 +""" diff --git a/tests/integration/type_system.lbd b/tests/integration/type_system.lbd new file mode 100644 index 0000000..17d20d3 --- /dev/null +++ b/tests/integration/type_system.lbd @@ -0,0 +1,12 @@ +identity: Number -> Number = \x: Number. x + +apply: (Number -> Number) -> Number -> Number = \f: (Number -> Number). \x: Number. (f x) + +factorial: Number -> Number = \n: Number. + (null (cmp 0 n) 1 (mul n (factorial (sub n 1.0)))) + +double: Number -> Number = \x: Number. (mul x 2.0) + +(print (identity 42) "\n") +(print (apply double 21) "\n") +(print (factorial 5) "\n") diff --git a/tests/integration/type_system.test.toml b/tests/integration/type_system.test.toml new file mode 100644 index 0000000..0481fa4 --- /dev/null +++ b/tests/integration/type_system.test.toml @@ -0,0 +1,9 @@ +file = "type_system.lbd" + +[expect] +exit_code = 0 +stdout = """ +42.000000 +42.000000 +120.000000 +""" diff --git a/tests/integration/variadic_functions.lbd b/tests/integration/variadic_functions.lbd new file mode 100644 index 0000000..3836dfb --- /dev/null +++ b/tests/integration/variadic_functions.lbd @@ -0,0 +1,11 @@ +listOps: List = (list 10 20 30 40 50) +listSize1: Number = (listSize listOps) + +doubleAll: List = (map \x: Number. (mul x 2.0) listOps) +doubleSize: Number = (listSize doubleAll) + +foldSum: Number = (foldRight \x: Number. \y: Number. (add x y) 0 listOps) + +(print listSize1 "\n") +(print doubleSize "\n") +(print foldSum "\n") diff --git a/tests/integration/variadic_functions.test.toml b/tests/integration/variadic_functions.test.toml new file mode 100644 index 0000000..69f3834 --- /dev/null +++ b/tests/integration/variadic_functions.test.toml @@ -0,0 +1,9 @@ +file = "variadic_functions.lbd" + +[expect] +exit_code = 0 +stdout = """ +5.000000 +5.000000 +150.000000 +"""