aboutsummaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 17:32:24 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 17:32:24 +0000
commitf19f1c956780f41f1440b372187770ce0be8efed (patch)
treefe0c2fe47a2cddb1bd8bd92206d47136bfcbfd37 /include
parent3366a2e0fae05d9e792169e74471c7282bf18681 (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.h14
-rw-r--r--include/cps/RuntimeCheck.h11
-rw-r--r--include/cps/TypeSystem.h91
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