593 lines
19 KiB
ObjectPascal
593 lines
19 KiB
ObjectPascal
unit Myc.Ast.Types;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Generics.Collections,
|
|
Myc.Data.Scalar;
|
|
|
|
type
|
|
IStaticType = interface;
|
|
|
|
// Defines the categories of types available in the language.
|
|
TStaticTypeKind = (
|
|
stUnknown, // Used during inference before a type is known
|
|
stVoid,
|
|
stOrdinal, // Int64
|
|
stFloat, // Double
|
|
stText,
|
|
stMethod,
|
|
stSeries,
|
|
stRecord,
|
|
stRecordSeries
|
|
);
|
|
|
|
TStaticTypeKindHelper = record helper for TStaticTypeKind
|
|
public
|
|
function ToString: string;
|
|
end;
|
|
|
|
// Defines the signature of a method
|
|
IMethodSignature = interface
|
|
{$region 'private'}
|
|
function GetParamTypes: TArray<IStaticType>;
|
|
function GetReturnType: IStaticType;
|
|
{$endregion}
|
|
property ParamTypes: TArray<IStaticType> read GetParamTypes;
|
|
property ReturnType: IStaticType read GetReturnType;
|
|
end;
|
|
|
|
// Represents a complete static type definition.
|
|
IStaticType = interface
|
|
{$region 'private'}
|
|
function GetKind: TStaticTypeKind;
|
|
function GetElementType: IStaticType;
|
|
function GetSignature: IMethodSignature;
|
|
function GetDefinition: TScalarRecordDefinition;
|
|
{$endregion}
|
|
|
|
// The kind of type (e.g., Ordinal, Series, etc.)
|
|
property Kind: TStaticTypeKind read GetKind;
|
|
// The element type (if Kind = stSeries)
|
|
property ElementType: IStaticType read GetElementType;
|
|
// The signature (if Kind = stMethod)
|
|
property Signature: IMethodSignature read GetSignature;
|
|
// The definition (if Kind = stRecord or stRecordSeries)
|
|
property Definition: TScalarRecordDefinition read GetDefinition;
|
|
|
|
// Checks for type equality
|
|
function IsEqual(const Other: IStaticType): Boolean;
|
|
function ToString: string;
|
|
end;
|
|
|
|
ETypeException = class(Exception);
|
|
|
|
// Factory and Flyweight access for static types.
|
|
// Use this record to create or access all IStaticType instances.
|
|
TTypes = record
|
|
private
|
|
class var
|
|
FUnknown: IStaticType;
|
|
class var
|
|
FVoid: IStaticType;
|
|
class var
|
|
FOrdinal: IStaticType;
|
|
class var
|
|
FFloat: IStaticType;
|
|
class var
|
|
FText: IStaticType;
|
|
class constructor Create;
|
|
public
|
|
// Flyweight accessors for simple types
|
|
class property Unknown: IStaticType read FUnknown;
|
|
class property Void: IStaticType read FVoid;
|
|
class property Ordinal: IStaticType read FOrdinal;
|
|
class property Float: IStaticType read FFloat;
|
|
class property Text: IStaticType read FText;
|
|
|
|
// Factory functions for complex types
|
|
class function CreateSeries(const AElementType: IStaticType): IStaticType; static;
|
|
class function CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType; static;
|
|
class function CreateRecord(const ADef: TScalarRecordDefinition): IStaticType; static;
|
|
class function CreateRecordSeries(const ADef: TScalarRecordDefinition): IStaticType; static;
|
|
|
|
class function FromScalarKind(AKind: TScalar.TKind): IStaticType; static;
|
|
end;
|
|
|
|
// Defines the rules of the type system.
|
|
TTypeRules = record
|
|
public
|
|
class function Promote(const A, B: IStaticType): IStaticType; static;
|
|
|
|
// Checks if a value of type 'Source' can be assigned to 'Target'.
|
|
class function CanAssign(const Target, Source: IStaticType): Boolean; static;
|
|
// Determines the result type of a binary operation.
|
|
// Raises ETypeException on failure.
|
|
class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static;
|
|
// Determines the result type of a unary operation.
|
|
class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Generics.Defaults;
|
|
|
|
{ TStaticTypeKindHelper }
|
|
|
|
function TStaticTypeKindHelper.ToString: string;
|
|
begin
|
|
case Self of
|
|
stUnknown: Result := 'Unknown';
|
|
stVoid: Result := 'Void';
|
|
stOrdinal: Result := 'Ordinal';
|
|
stFloat: Result := 'Float';
|
|
stText: Result := 'Text';
|
|
stMethod: Result := 'Method';
|
|
stSeries: Result := 'Series';
|
|
stRecord: Result := 'Record';
|
|
stRecordSeries: Result := 'RecordSeries';
|
|
else
|
|
Result := 'ErrorType';
|
|
end;
|
|
end;
|
|
|
|
// --- Abstract Base Implementation ---
|
|
|
|
type
|
|
TAbstractStaticType = class(TInterfacedObject, IStaticType)
|
|
protected
|
|
// IStaticType (default implementations for non-applicable properties)
|
|
function GetKind: TStaticTypeKind; virtual; abstract;
|
|
function GetElementType: IStaticType; virtual;
|
|
function GetSignature: IMethodSignature; virtual;
|
|
function GetDefinition: TScalarRecordDefinition; virtual;
|
|
function IsEqual(const Other: IStaticType): Boolean; virtual;
|
|
function ToString: string; override;
|
|
end;
|
|
|
|
function TAbstractStaticType.GetElementType: IStaticType;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TAbstractStaticType.GetSignature: IMethodSignature;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TAbstractStaticType.GetDefinition: TScalarRecordDefinition;
|
|
begin
|
|
// Return an empty/invalid definition
|
|
Result := Default(TScalarRecordDefinition);
|
|
end;
|
|
|
|
function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean;
|
|
begin
|
|
// Default implementation: types are equal if their kinds are equal.
|
|
// Complex types (Series, Record, Method) MUST override this.
|
|
if not Assigned(Other) then
|
|
exit(False);
|
|
Result := (GetKind = Other.Kind);
|
|
end;
|
|
|
|
function TAbstractStaticType.ToString: string;
|
|
begin
|
|
Result := GetKind.ToString;
|
|
end;
|
|
|
|
// --- Simple (Flyweight) Type Implementations ---
|
|
|
|
type
|
|
TSimpleStaticType = class(TAbstractStaticType)
|
|
private
|
|
FKind: TStaticTypeKind;
|
|
public
|
|
constructor Create(AKind: TStaticTypeKind);
|
|
function GetKind: TStaticTypeKind; override;
|
|
end;
|
|
|
|
constructor TSimpleStaticType.Create(AKind: TStaticTypeKind);
|
|
begin
|
|
inherited Create;
|
|
FKind := AKind;
|
|
end;
|
|
|
|
function TSimpleStaticType.GetKind: TStaticTypeKind;
|
|
begin
|
|
Result := FKind;
|
|
end;
|
|
|
|
// --- Complex Type Implementations ---
|
|
|
|
type
|
|
TSeriesType = class(TAbstractStaticType)
|
|
private
|
|
FElementType: IStaticType;
|
|
public
|
|
constructor Create(AElementType: IStaticType);
|
|
function GetKind: TStaticTypeKind; override;
|
|
function GetElementType: IStaticType; override;
|
|
function IsEqual(const Other: IStaticType): Boolean; override;
|
|
function ToString: string; override;
|
|
end;
|
|
|
|
constructor TSeriesType.Create(AElementType: IStaticType);
|
|
begin
|
|
inherited Create;
|
|
FElementType := AElementType;
|
|
end;
|
|
|
|
function TSeriesType.GetKind: TStaticTypeKind;
|
|
begin
|
|
Result := stSeries;
|
|
end;
|
|
|
|
function TSeriesType.GetElementType: IStaticType;
|
|
begin
|
|
Result := FElementType;
|
|
end;
|
|
|
|
function TSeriesType.IsEqual(const Other: IStaticType): Boolean;
|
|
begin
|
|
Result := (Assigned(Other)) and (Other.Kind = stSeries) and (Self.FElementType.IsEqual(Other.ElementType));
|
|
end;
|
|
|
|
function TSeriesType.ToString: string;
|
|
begin
|
|
Result := 'Series<' + FElementType.ToString + '>';
|
|
end;
|
|
|
|
// ---
|
|
type
|
|
TMethodSignature = class(TInterfacedObject, IMethodSignature)
|
|
private
|
|
FParamTypes: TArray<IStaticType>;
|
|
FReturnType: IStaticType;
|
|
function GetParamTypes: TArray<IStaticType>;
|
|
function GetReturnType: IStaticType;
|
|
public
|
|
constructor Create(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType);
|
|
end;
|
|
|
|
constructor TMethodSignature.Create(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType);
|
|
begin
|
|
inherited Create;
|
|
FParamTypes := AParamTypes;
|
|
FReturnType := AReturnType;
|
|
end;
|
|
|
|
function TMethodSignature.GetParamTypes: TArray<IStaticType>;
|
|
begin
|
|
Result := FParamTypes;
|
|
end;
|
|
|
|
function TMethodSignature.GetReturnType: IStaticType;
|
|
begin
|
|
Result := FReturnType;
|
|
end;
|
|
|
|
// ---
|
|
type
|
|
TMethodType = class(TAbstractStaticType)
|
|
private
|
|
FSignature: IMethodSignature;
|
|
public
|
|
constructor Create(AParamTypes: TArray<IStaticType>; AReturnType: IStaticType);
|
|
function GetKind: TStaticTypeKind; override;
|
|
function GetSignature: IMethodSignature; override;
|
|
function IsEqual(const Other: IStaticType): Boolean; override;
|
|
function ToString: string; override;
|
|
end;
|
|
|
|
constructor TMethodType.Create(AParamTypes: TArray<IStaticType>; AReturnType: IStaticType);
|
|
begin
|
|
inherited Create;
|
|
FSignature := TMethodSignature.Create(AParamTypes, AReturnType);
|
|
end;
|
|
|
|
function TMethodType.GetKind: TStaticTypeKind;
|
|
begin
|
|
Result := stMethod;
|
|
end;
|
|
|
|
function TMethodType.GetSignature: IMethodSignature;
|
|
begin
|
|
Result := FSignature;
|
|
end;
|
|
|
|
function TMethodType.IsEqual(const Other: IStaticType): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if (not Assigned(Other)) or (Other.Kind <> stMethod) then
|
|
exit(False);
|
|
|
|
var otherSig := Other.Signature;
|
|
if not Self.FSignature.ReturnType.IsEqual(otherSig.ReturnType) then
|
|
exit(False);
|
|
|
|
if Length(Self.FSignature.ParamTypes) <> Length(otherSig.ParamTypes) then
|
|
exit(False);
|
|
|
|
for i := 0 to High(Self.FSignature.ParamTypes) do
|
|
begin
|
|
if not Self.FSignature.ParamTypes[i].IsEqual(otherSig.ParamTypes[i]) then
|
|
exit(False);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TMethodType.ToString: string;
|
|
var
|
|
i: Integer;
|
|
paramStr: string;
|
|
begin
|
|
paramStr := '';
|
|
for i := 0 to High(FSignature.ParamTypes) do
|
|
begin
|
|
paramStr := paramStr + FSignature.ParamTypes[i].ToString;
|
|
if i < High(FSignature.ParamTypes) then
|
|
paramStr := paramStr + ', ';
|
|
end;
|
|
Result := Format('Method(%s): %s', [paramStr, FSignature.ReturnType.ToString]);
|
|
end;
|
|
|
|
// ---
|
|
type
|
|
TRecordType = class(TAbstractStaticType)
|
|
private
|
|
FKind: TStaticTypeKind;
|
|
FDefinition: TScalarRecordDefinition;
|
|
public
|
|
constructor Create(AKind: TStaticTypeKind; ADef: TScalarRecordDefinition);
|
|
function GetKind: TStaticTypeKind; override;
|
|
function GetDefinition: TScalarRecordDefinition; override;
|
|
function IsEqual(const Other: IStaticType): Boolean; override;
|
|
function ToString: string; override;
|
|
end;
|
|
|
|
constructor TRecordType.Create(AKind: TStaticTypeKind; ADef: TScalarRecordDefinition);
|
|
begin
|
|
inherited Create;
|
|
Assert(AKind in [stRecord, stRecordSeries]);
|
|
FKind := AKind;
|
|
FDefinition := ADef;
|
|
end;
|
|
|
|
function TRecordType.GetKind: TStaticTypeKind;
|
|
begin
|
|
Result := FKind;
|
|
end;
|
|
|
|
function TRecordType.GetDefinition: TScalarRecordDefinition;
|
|
begin
|
|
Result := FDefinition;
|
|
end;
|
|
|
|
function TRecordType.IsEqual(const Other: IStaticType): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if (not Assigned(Other)) or (Other.Kind <> GetKind) then
|
|
exit(False);
|
|
|
|
var otherDef := Other.Definition;
|
|
if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then
|
|
exit(False);
|
|
|
|
for i := 0 to High(Self.FDefinition.Fields) do
|
|
begin
|
|
if (Self.FDefinition.Fields[i].Name <> otherDef.Fields[i].Name) or (Self.FDefinition.Fields[i].Kind <> otherDef.Fields[i].Kind) then
|
|
exit(False);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TRecordType.ToString: string;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := GetKind.ToString + '{';
|
|
for i := 0 to High(FDefinition.Fields) do
|
|
begin
|
|
Result := Result + FDefinition.Fields[i].Name + ': ' + FDefinition.Fields[i].Kind.ToString;
|
|
if i < High(FDefinition.Fields) then
|
|
Result := Result + ', ';
|
|
end;
|
|
Result := Result + '}';
|
|
end;
|
|
|
|
{ TTypes (Factory) }
|
|
|
|
class constructor TTypes.Create;
|
|
begin
|
|
// Create the flyweight singletons
|
|
FUnknown := TSimpleStaticType.Create(stUnknown);
|
|
FVoid := TSimpleStaticType.Create(stVoid);
|
|
FOrdinal := TSimpleStaticType.Create(stOrdinal);
|
|
FFloat := TSimpleStaticType.Create(stFloat);
|
|
FText := TSimpleStaticType.Create(stText);
|
|
end;
|
|
|
|
class function TTypes.CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType;
|
|
begin
|
|
Result := TMethodType.Create(AParamTypes, AReturnType);
|
|
end;
|
|
|
|
class function TTypes.CreateRecord(const ADef: TScalarRecordDefinition): IStaticType;
|
|
begin
|
|
Result := TRecordType.Create(stRecord, ADef);
|
|
end;
|
|
|
|
class function TTypes.CreateRecordSeries(const ADef: TScalarRecordDefinition): IStaticType;
|
|
begin
|
|
Result := TRecordType.Create(stRecordSeries, ADef);
|
|
end;
|
|
|
|
class function TTypes.CreateSeries(const AElementType: IStaticType): IStaticType;
|
|
begin
|
|
Result := TSeriesType.Create(AElementType);
|
|
end;
|
|
|
|
class function TTypes.FromScalarKind(AKind: TScalar.TKind): IStaticType;
|
|
begin
|
|
case AKind of
|
|
TScalar.TKind.Ordinal: Result := FOrdinal;
|
|
TScalar.TKind.Float: Result := FFloat;
|
|
else
|
|
raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.');
|
|
end;
|
|
end;
|
|
|
|
{ TTypeRules }
|
|
|
|
class function TTypeRules.CanAssign(const Target, Source: IStaticType): Boolean;
|
|
begin
|
|
if (not Assigned(Target)) or (not Assigned(Source)) then
|
|
exit(False);
|
|
|
|
// During inference, allow assignment involving Unknown
|
|
if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then
|
|
exit(True);
|
|
|
|
if Target.IsEqual(Source) then
|
|
exit(True);
|
|
|
|
// Allow assigning an integer (Ordinal) to a float variable.
|
|
if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then
|
|
exit(True);
|
|
|
|
if (Target.Kind = stVoid) and (Source.Kind = stMethod) then
|
|
exit(True);
|
|
|
|
// TODO: Implement full assignment compatibility rules (e.g., for records/series)
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
class function TTypeRules.Promote(const A, B: IStaticType): IStaticType;
|
|
begin
|
|
// Handle Unknown: If one is Unknown, the result is the other type.
|
|
if A.Kind = stUnknown then
|
|
exit(B);
|
|
if B.Kind = stUnknown then
|
|
exit(A);
|
|
|
|
// all operations involving void result void
|
|
if (A.Kind = stVoid) or (B.Kind = stVoid) then
|
|
exit(TTypes.Void);
|
|
|
|
// Standard Numeric promotion rule: Float wins
|
|
if (A.Kind = stFloat) and (B.Kind = stFloat) then
|
|
exit(TTypes.Float);
|
|
if (A.Kind = stFloat) and (B.Kind = stOrdinal) then
|
|
exit(TTypes.Float);
|
|
if (A.Kind = stOrdinal) and (B.Kind = stFloat) then
|
|
exit(TTypes.Float);
|
|
if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then
|
|
exit(TTypes.Ordinal);
|
|
|
|
// If types are identical and not numeric, return that type (e.g. Text + Text might be valid later)
|
|
if A.IsEqual(B) then
|
|
exit(A);
|
|
|
|
// Cannot promote other combinations during basic inference
|
|
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
|
|
end;
|
|
|
|
class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType;
|
|
var
|
|
promotedType: IStaticType;
|
|
promotedKind: TStaticTypeKind;
|
|
begin
|
|
// 1. Determine the effective type after promotion, handling Unknown.
|
|
try
|
|
promotedType := Promote(Left, Right);
|
|
except
|
|
// If promotion fails (e.g., Text and Ordinal), the operation is invalid.
|
|
on E: ETypeException do
|
|
raise ETypeException.CreateFmt(
|
|
'Operator %s cannot be applied to %s and %s (promotion failed: %s)',
|
|
[Op.ToString, Left.ToString, Right.ToString, E.Message]);
|
|
end;
|
|
|
|
// If promotion results in Unknown, the result type is also Unknown for now.
|
|
if promotedType.Kind = stUnknown then
|
|
exit(TTypes.Unknown);
|
|
|
|
// 2. Check if the operator is valid for the promoted type.
|
|
promotedKind := promotedType.Kind;
|
|
|
|
case Op of
|
|
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
|
|
begin
|
|
// Numeric operations require Ordinal or Float
|
|
if not (promotedKind in [stOrdinal, stFloat]) then
|
|
raise ETypeException
|
|
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]);
|
|
Result := promotedType; // Result has the promoted type
|
|
end;
|
|
|
|
TScalar.TBinaryOp.Divide:
|
|
begin
|
|
// Division requires Ordinal or Float, but *always* results in Float
|
|
if not (promotedKind in [stOrdinal, stFloat]) then
|
|
raise ETypeException
|
|
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]);
|
|
Result := TTypes.Float;
|
|
end;
|
|
|
|
TScalar.TBinaryOp.Equal,
|
|
TScalar.TBinaryOp.NotEqual,
|
|
TScalar.TBinaryOp.Less,
|
|
TScalar.TBinaryOp.Greater,
|
|
TScalar.TBinaryOp.LessOrEqual,
|
|
TScalar.TBinaryOp.GreaterOrEqual:
|
|
begin
|
|
// Comparison requires Ordinal or Float, but *always* results in Ordinal (boolean)
|
|
if not (promotedKind in [stOrdinal, stFloat]) then
|
|
raise ETypeException.CreateFmt(
|
|
'Comparison operator %s requires Ordinal or Float, but got %s after promotion',
|
|
[Op.ToString, promotedType.ToString]);
|
|
Result := TTypes.Ordinal;
|
|
end;
|
|
else
|
|
raise ETypeException.Create('Unknown binary operator for type resolution.');
|
|
end;
|
|
end;
|
|
|
|
class function TTypeRules.ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType;
|
|
var
|
|
rightKind: TStaticTypeKind;
|
|
begin
|
|
rightKind := Right.Kind;
|
|
|
|
// If operand is Unknown, result is Unknown.
|
|
if rightKind = stUnknown then
|
|
exit(TTypes.Unknown);
|
|
|
|
case Op of
|
|
TScalar.TUnaryOp.Negate:
|
|
begin
|
|
if not (rightKind in [stOrdinal, stFloat]) then
|
|
raise ETypeException.CreateFmt('Unary negation cannot be applied to %s', [Right.ToString]);
|
|
Result := Right; // Negation preserves type
|
|
end;
|
|
|
|
TScalar.TUnaryOp.Not:
|
|
begin
|
|
if not (rightKind = stOrdinal) then
|
|
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]);
|
|
Result := TTypes.Ordinal;
|
|
end;
|
|
else
|
|
raise ETypeException.Create('Unknown unary operator for type resolution.');
|
|
end;
|
|
end;
|
|
|
|
end.
|