diff options
| author | _ov4 <ov4@ov4.org> | 2026-07-26 17:32:24 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-07-26 17:32:24 +0000 |
| commit | f19f1c956780f41f1440b372187770ce0be8efed (patch) | |
| tree | fe0c2fe47a2cddb1bd8bd92206d47136bfcbfd37 /include | |
| parent | 3366a2e0fae05d9e792169e74471c7282bf18681 (diff) | |
feat: user type registry and enumerated types (CIE 4.1/4.2)
- TypeSystem: TypeKinds Enum/Record/Pointer with per-kind payloads and
checked accessors; two-pass registration (names first, then definitions
in source order) so pointer TYPEs may forward-reference; enum-constant
table owned by TypeSystem so constants stay visible inside function
bodies across the Symbols save/clear/restore. Dead surface removed
(Custom kind, Printable/Builtin/ReferenceLike, ElementSize, hasType,
CodeGen::getNamedValue/getLLVMType).
- Enums are i64 with 0-based ordinals (constexpr kEnumOrdinalBase); all
type identity is by NAME. New emitCoercedExpr is the single admission
gate, replacing raw coercion at assignments, array stores, BYVAL args,
RETURN, IF/WHILE/REPEAT/FOR operands, file operands and builtin args;
coerceValueToType now hard-errors if a user-kind target reaches it.
- Binary ops with user-kind operands are intercepted ahead of
ArithmeticHandler ('&'-intercept precedent): same-enum comparisons,
enum +/- INTEGER (with a runtime range check, [Fatal] on overflow, the
only site that can manufacture an invalid ordinal), same-name pointer
=/<>; everything else is a compile error naming the operator.
- OUTPUT is an exhaustive switch over TypeKind (enum prints its ordinal;
record/pointer are compile errors, also covering whole-array OUTPUT);
INPUT's silent %lld fallback for unknown kinds is now an explicit error.
- Enum value names are globally reserved: collisions with DECLARE,
array names, parameters and function names are compile errors.
- RuntimeCheck: emitEnumRangeCheck + emitNullDerefCheck (used by the
pointer phase) following the emitIndexCheck recipe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'include')
| -rw-r--r-- | include/cps/CodeGen.h | 14 | ||||
| -rw-r--r-- | include/cps/RuntimeCheck.h | 11 | ||||
| -rw-r--r-- | include/cps/TypeSystem.h | 91 |
3 files changed, 98 insertions, 18 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 2451cbd..713d02b 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -64,6 +64,10 @@ class CodeGen { bool isWholeArrayVar(ExprAST *Expr) const; const TypeInfo *getExprTypeInfo(ExprAST *Expr) const; llvm::Value *coerceValueToType(llvm::Value *Val, const TypeInfo *TargetInfo); + + // TYPE-system helpers (Designator.cc). + void runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements); + llvm::Value *emitUserKindBinaryOp(BinaryExprAST *Bin, const TypeInfo *LT, const TypeInfo *RT); bool marshalCallArgs(const std::string &Callee, const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, std::vector<llvm::Value*> &Out); @@ -90,10 +94,14 @@ public: // STRING slots zero-init to null; normalize null to "" at load/print sites. llvm::Value *emitStringNullGuard(llvm::Value *StrVal); + // Name-level admission gate for Enum/Record/Pointer plus the existing + // representation coercion for builtin targets. The ONLY door through + // which assignments, BYVAL args, RETURN values and file/builtin operands + // may produce a typed value. (Designator.cc) + llvm::Value *emitCoercedExpr(ExprAST *E, const TypeInfo *TargetInfo); + const TypeInfo *resolveType(const std::string &TypeName) const; - llvm::Type *getLLVMType(const std::string &TypeName) const; - llvm::Value *getNamedValue(const std::string &Name) const; - + friend class ArrayHandler; }; diff --git a/include/cps/RuntimeCheck.h b/include/cps/RuntimeCheck.h index 97f4765..5996409 100644 --- a/include/cps/RuntimeCheck.h +++ b/include/cps/RuntimeCheck.h @@ -2,6 +2,8 @@ #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" +#include <cstdint> +#include <string> namespace cps { @@ -14,6 +16,8 @@ class RuntimeCheck { llvm::FunctionCallee ExitFunc; llvm::Value *DivZeroMsg; llvm::Value *OutOfBoundsMsg; + llvm::Value *EnumRangeMsg; + llvm::Value *NullDerefMsg; void setupExternalFunctions(); void emitErrorAndExit(llvm::Value *Condition, llvm::Value *Msg, int Line); @@ -24,6 +28,13 @@ public: void emitDivZeroCheck(llvm::Value *Divisor, int Line); void emitIndexCheck(llvm::Value *Index, llvm::Value *Lower, llvm::Value *Upper, int Line); + // Enum +/- INTEGER results must stay inside the enum's value range. + void emitEnumRangeCheck(llvm::Value *Ordinal, uint64_t ValueCount, + const std::string &TypeName, int Line); + + // Dereferencing an unset (null) pointer dies instead of segfaulting. + void emitNullDerefCheck(llvm::Value *Ptr, int Line); + // Runtime-computed variant: FmtArgs are the printf varargs after Msg // (e.g. {i32 line, ptr filename}). Needed when the failure site is inside // a synthesized helper function, where the line number is an argument diff --git a/include/cps/TypeSystem.h b/include/cps/TypeSystem.h index 14a21bd..06ffe96 100644 --- a/include/cps/TypeSystem.h +++ b/include/cps/TypeSystem.h @@ -6,9 +6,18 @@ #include <cstdint> #include <map> #include <string> +#include <utility> +#include <vector> namespace cps { +// Enum ordinals are 0-based: DECLARE zero-init and array memset(0) then +// produce the first enumerator, a valid value. +inline constexpr int64_t kEnumOrdinalBase = 0; + +// LLVM struct name prefix for record types: %record.NAME +inline constexpr const char *kRecordStructPrefix = "record."; + enum class TypeKind { Integer, Real, @@ -16,17 +25,45 @@ enum class TypeKind { String, Char, Void, - Custom + Enum, + Record, + Pointer +}; + +// ---- Per-kind payloads (filled only for the matching TypeKind) ---- + +struct RecordFieldInfo { + std::string TypeName; + unsigned Index = 0; // struct field index +}; + +struct RecordPayload { + std::vector<std::pair<std::string, RecordFieldInfo>> Fields; // declaration order + const RecordFieldInfo *findField(const std::string &Name) const; +}; + +struct EnumPayload { + std::vector<std::string> ValueNames; // ordinal = position + kEnumOrdinalBase +}; + +struct PointerPayload { + std::string PointeeTypeName; +}; + +// A bare enum value name, resolvable in expression position. +struct EnumConstant { + std::string TypeName; + int64_t Ordinal = 0; }; struct TypeInfo { std::string Name; llvm::Type *LLVMType = nullptr; - uint64_t ElementSize = 0; - TypeKind Kind = TypeKind::Custom; - bool Printable = false; - bool Builtin = false; - bool ReferenceLike = false; + TypeKind Kind = TypeKind::Void; + + RecordPayload Record; // Kind == Record only + EnumPayload Enum; // Kind == Enum only + PointerPayload Pointer; // Kind == Pointer only bool isNumeric() const; bool isIntegral() const; @@ -35,6 +72,19 @@ struct TypeInfo { bool isString() const; bool isChar() const; bool isVoid() const; + bool isEnum() const; + bool isRecord() const; + bool isPointer() const; + // Enum/Record/Pointer: type identity is the NAME; representation-level + // coercion must never be reached without a prior name-level check. + bool isUserKind() const; + // OUTPUT admissibility, derived from Kind (enums print their ordinal). + bool isOutputtable() const; + + // Checked payload accessors: nullptr unless Kind matches. + const RecordPayload *asRecord() const; + const EnumPayload *asEnum() const; + const PointerPayload *asPointer() const; }; struct SymbolInfo { @@ -46,25 +96,36 @@ struct SymbolInfo { class TypeSystem { llvm::LLVMContext &Context; std::map<std::string, TypeInfo> Types; + std::map<std::string, EnumConstant> EnumConstants; void registerBuiltins(); + const TypeInfo *registerType(const std::string &TypeName, + llvm::Type *LLVMType, + TypeKind Kind); public: explicit TypeSystem(llvm::LLVMContext &Ctx); const TypeInfo *resolve(const std::string &TypeName) const; - bool hasType(const std::string &TypeName) const; - const TypeInfo *registerType(const std::string &TypeName, - llvm::Type *LLVMType, - uint64_t ElementSize, - TypeKind Kind = TypeKind::Custom, - bool Printable = false, - bool ReferenceLike = false, - bool Builtin = false); + // Two-pass user-type registration (pass A then pass B, both in source + // order). Pass A reserves every name so pointer pointees may forward- + // reference; pass B fills definitions. On failure Err carries the + // diagnostic and nothing is (further) registered. + bool declareUserType(const std::string &Name, TypeKind Kind, std::string &Err); + bool defineEnum(const std::string &Name, + const std::vector<std::string> &Values, + std::string &Err); + bool defineRecord(const std::string &Name, + const std::vector<std::pair<std::string, std::string>> &Fields, + std::string &Err); + bool definePointer(const std::string &Name, + const std::string &PointeeName, + std::string &Err); + + const EnumConstant *lookupEnumConstant(const std::string &Name) const; llvm::Type *getLLVMType(const std::string &TypeName) const; - uint64_t getElementSize(const std::string &TypeName) const; llvm::Constant *getZeroValue(const std::string &TypeName) const; // Target-independent sizeof: a null-GEP+ptrtoint i64 constant the backend |
