375 lines
13 KiB
ObjectPascal
375 lines
13 KiB
ObjectPascal
unit Myc.Ast.Types;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Generics.Collections,
|
|
Myc.Data.Scalar;
|
|
|
|
type
|
|
// 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;
|
|
|
|
// Represents a complete static type definition.
|
|
// This is a managed record, passed by value, cheap to copy.
|
|
TStaticType = record
|
|
public
|
|
type
|
|
// Forward declaration for mutual recursion
|
|
PStaticType = ^TStaticType;
|
|
|
|
// Defines the signature of a method
|
|
IMethodSignature = interface
|
|
{$region 'private'}
|
|
function GetParamTypes: TArray<TStaticType>;
|
|
function GetReturnType: TStaticType;
|
|
{$endregion}
|
|
property ParamTypes: TArray<TStaticType> read GetParamTypes;
|
|
property ReturnType: TStaticType read GetReturnType;
|
|
end;
|
|
|
|
public
|
|
Kind: TStaticTypeKind;
|
|
// Used if Kind = stMethod
|
|
Signature: IMethodSignature;
|
|
// Used if Kind = stSeries
|
|
ElementType: PStaticType;
|
|
// Used if Kind = stRecord or stRecordSeries
|
|
Definition: TScalarRecordDefinition;
|
|
|
|
class operator Initialize(out Dest: TStaticType);
|
|
class operator Finalize(var Dest: TStaticType);
|
|
|
|
// Factory functions
|
|
class function Create(AKind: TStaticTypeKind): TStaticType; static;
|
|
class function CreateSeries(const AElementType: TStaticType): TStaticType; static;
|
|
class function CreateMethod(const AParamTypes: TArray<TStaticType>; const AReturnType: TStaticType): TStaticType; static;
|
|
class function CreateRecord(const ADef: TScalarRecordDefinition): TStaticType; static;
|
|
class function CreateRecordSeries(const ADef: TScalarRecordDefinition): TStaticType; static;
|
|
|
|
class function FromScalarKind(AKind: TScalar.TKind): TStaticType; static;
|
|
|
|
function ToString: string;
|
|
class operator Equal(const A, B: TStaticType): Boolean;
|
|
class operator NotEqual(A, B: TStaticType): Boolean; inline;
|
|
end;
|
|
|
|
ETypeException = class(Exception);
|
|
|
|
// Defines the rules of the type system.
|
|
TTypeRules = record
|
|
private
|
|
class function Promote(const A, B: TStaticType): TStaticType; static;
|
|
public
|
|
// Checks if a value of type 'Source' can be assigned to 'Target'.
|
|
class function CanAssign(const Target, Source: TStaticType): Boolean; static;
|
|
// Determines the result type of a binary operation.
|
|
// Raises ETypeException on failure.
|
|
class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: TStaticType): TStaticType; static;
|
|
// Determines the result type of a unary operation.
|
|
class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: TStaticType): TStaticType; static;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Generics.Defaults;
|
|
|
|
type
|
|
TMethodSignature = class(TInterfacedObject, TStaticType.IMethodSignature)
|
|
private
|
|
FParamTypes: TArray<TStaticType>;
|
|
FReturnType: TStaticType;
|
|
function GetParamTypes: TArray<TStaticType>;
|
|
function GetReturnType: TStaticType;
|
|
public
|
|
constructor Create(const AParamTypes: TArray<TStaticType>; const AReturnType: TStaticType);
|
|
end;
|
|
|
|
{ TMethodSignature }
|
|
|
|
constructor TMethodSignature.Create(const AParamTypes: TArray<TStaticType>; const AReturnType: TStaticType);
|
|
begin
|
|
inherited Create;
|
|
FParamTypes := AParamTypes;
|
|
FReturnType := AReturnType;
|
|
end;
|
|
|
|
function TMethodSignature.GetParamTypes: TArray<TStaticType>;
|
|
begin
|
|
Result := FParamTypes;
|
|
end;
|
|
|
|
function TMethodSignature.GetReturnType: TStaticType;
|
|
begin
|
|
Result := FReturnType;
|
|
end;
|
|
|
|
{ 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;
|
|
|
|
{ TStaticType }
|
|
|
|
class operator TStaticType.Initialize(out Dest: TStaticType);
|
|
begin
|
|
Dest.Kind := stUnknown;
|
|
Dest.Signature := nil;
|
|
Dest.ElementType := nil;
|
|
end;
|
|
|
|
class operator TStaticType.Finalize(var Dest: TStaticType);
|
|
begin
|
|
Dest.Signature := nil;
|
|
if Dest.ElementType <> nil then
|
|
begin
|
|
Finalize(Dest.ElementType^);
|
|
FreeMem(Dest.ElementType);
|
|
Dest.ElementType := nil;
|
|
end;
|
|
// TScalarRecordDefinition (FFields) is not owned by this record.
|
|
end;
|
|
|
|
class operator TStaticType.Equal(const A, B: TStaticType): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if A.Kind <> B.Kind then
|
|
exit(False);
|
|
|
|
case A.Kind of
|
|
stSeries: Result := A.ElementType^ = B.ElementType^;
|
|
stRecord, stRecordSeries:
|
|
begin
|
|
if Length(A.Definition.Fields) <> Length(B.Definition.Fields) then
|
|
exit(False);
|
|
for i := 0 to High(A.Definition.Fields) do
|
|
begin
|
|
if (A.Definition.Fields[i].Name <> B.Definition.Fields[i].Name)
|
|
or (A.Definition.Fields[i].Kind <> B.Definition.Fields[i].Kind) then
|
|
exit(False);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
stMethod:
|
|
begin
|
|
if A.Signature.ReturnType <> B.Signature.ReturnType then
|
|
exit(False);
|
|
if Length(A.Signature.ParamTypes) <> Length(B.Signature.ParamTypes) then
|
|
exit(False);
|
|
for i := 0 to High(A.Signature.ParamTypes) do
|
|
begin
|
|
if A.Signature.ParamTypes[i] <> B.Signature.ParamTypes[i] then
|
|
exit(False);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
else
|
|
// stUnknown, stVoid, stOrdinal, stFloat, stText are equal if their kinds are equal.
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
class function TStaticType.Create(AKind: TStaticTypeKind): TStaticType;
|
|
begin
|
|
Result.Kind := AKind;
|
|
end;
|
|
|
|
class function TStaticType.CreateMethod(const AParamTypes: TArray<TStaticType>; const AReturnType: TStaticType): TStaticType;
|
|
begin
|
|
Result.Kind := stMethod;
|
|
Result.Signature := TMethodSignature.Create(AParamTypes, AReturnType);
|
|
end;
|
|
|
|
class function TStaticType.CreateRecord(const ADef: TScalarRecordDefinition): TStaticType;
|
|
begin
|
|
Result.Kind := stRecord;
|
|
Result.Definition := ADef;
|
|
end;
|
|
|
|
class function TStaticType.CreateRecordSeries(const ADef: TScalarRecordDefinition): TStaticType;
|
|
begin
|
|
Result.Kind := stRecordSeries;
|
|
Result.Definition := ADef;
|
|
end;
|
|
|
|
class function TStaticType.CreateSeries(const AElementType: TStaticType): TStaticType;
|
|
begin
|
|
Result.Kind := stSeries;
|
|
New(Result.ElementType);
|
|
Result.ElementType^ := AElementType;
|
|
end;
|
|
|
|
class function TStaticType.FromScalarKind(AKind: TScalar.TKind): TStaticType;
|
|
begin
|
|
case AKind of
|
|
TScalar.TKind.Ordinal: Result.Kind := stOrdinal;
|
|
TScalar.TKind.Float: Result.Kind := stFloat;
|
|
else
|
|
raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.');
|
|
end;
|
|
end;
|
|
|
|
function TStaticType.ToString: string;
|
|
var
|
|
i: Integer;
|
|
paramStr: string;
|
|
begin
|
|
Result := Kind.ToString;
|
|
case Kind of
|
|
stSeries: Result := 'Series<' + ElementType^.ToString + '>';
|
|
stRecord, stRecordSeries:
|
|
begin
|
|
Result := Result + '{';
|
|
for i := 0 to High(Definition.Fields) do
|
|
begin
|
|
Result := Result + Definition.Fields[i].Name + ': ' + Definition.Fields[i].Kind.ToString;
|
|
if i < High(Definition.Fields) then
|
|
Result := Result + ', ';
|
|
end;
|
|
Result := Result + '}';
|
|
end;
|
|
stMethod:
|
|
begin
|
|
paramStr := '';
|
|
for i := 0 to High(Signature.ParamTypes) do
|
|
begin
|
|
paramStr := paramStr + Signature.ParamTypes[i].ToString;
|
|
if i < High(Signature.ParamTypes) then
|
|
paramStr := paramStr + ', ';
|
|
end;
|
|
Result := Format('Method(%s): %s', [paramStr, Signature.ReturnType.ToString]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class operator TStaticType.NotEqual(A, B: TStaticType): Boolean;
|
|
begin
|
|
Result := not (A = B);
|
|
end;
|
|
|
|
{ TTypeRules }
|
|
|
|
class function TTypeRules.CanAssign(const Target, Source: TStaticType): Boolean;
|
|
begin
|
|
if Target = Source then
|
|
exit(True);
|
|
|
|
// Allow assigning an integer (Ordinal) to a float variable.
|
|
if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then
|
|
exit(True);
|
|
|
|
// TODO: Implement full assignment compatibility rules (e.g., for records/series)
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
class function TTypeRules.Promote(const A, B: TStaticType): TStaticType;
|
|
begin
|
|
// Numeric promotion rule: Float wins
|
|
if (A.Kind = stFloat) and (B.Kind = stFloat) then
|
|
exit(TStaticType.Create(stFloat));
|
|
if (A.Kind = stFloat) and (B.Kind = stOrdinal) then
|
|
exit(TStaticType.Create(stFloat));
|
|
if (A.Kind = stOrdinal) and (B.Kind = stFloat) then
|
|
exit(TStaticType.Create(stFloat));
|
|
if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then
|
|
exit(TStaticType.Create(stOrdinal));
|
|
|
|
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
|
|
end;
|
|
|
|
class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: TStaticType): TStaticType;
|
|
begin
|
|
case Op of
|
|
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
|
|
begin
|
|
// Numeric operations: Promote and return
|
|
if (Left.Kind in [stOrdinal, stFloat]) and (Right.Kind in [stOrdinal, stFloat]) then
|
|
Result := Promote(Left, Right)
|
|
else
|
|
raise ETypeException.CreateFmt('Operator %s cannot be applied to %s and %s', [Op.ToString, Left.ToString, Right.ToString]);
|
|
end;
|
|
|
|
TScalar.TBinaryOp.Divide:
|
|
begin
|
|
// Division *always* results in Float, even Ordinal / Ordinal
|
|
if (Left.Kind in [stOrdinal, stFloat]) and (Right.Kind in [stOrdinal, stFloat]) then
|
|
Result := TStaticType.Create(stFloat)
|
|
else
|
|
raise ETypeException.CreateFmt('Operator %s cannot be applied to %s and %s', [Op.ToString, Left.ToString, Right.ToString]);
|
|
end;
|
|
|
|
TScalar.TBinaryOp.Equal,
|
|
TScalar.TBinaryOp.NotEqual,
|
|
TScalar.TBinaryOp.Less,
|
|
TScalar.TBinaryOp.Greater,
|
|
TScalar.TBinaryOp.LessOrEqual,
|
|
TScalar.TBinaryOp.GreaterOrEqual:
|
|
begin
|
|
// Comparison operations: Check if promotion is possible, but always return Ordinal (boolean)
|
|
if (Left.Kind in [stOrdinal, stFloat]) and (Right.Kind in [stOrdinal, stFloat]) then
|
|
Result := TStaticType.Create(stOrdinal)
|
|
else
|
|
raise ETypeException
|
|
.CreateFmt('Comparison operator %s cannot be applied to %s and %s', [Op.ToString, Left.ToString, Right.ToString]);
|
|
end;
|
|
else
|
|
raise ETypeException.Create('Unknown binary operator for type resolution.');
|
|
end;
|
|
end;
|
|
|
|
class function TTypeRules.ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: TStaticType): TStaticType;
|
|
begin
|
|
case Op of
|
|
TScalar.TUnaryOp.Negate:
|
|
begin
|
|
if not (Right.Kind 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 (Right.Kind = stOrdinal) then
|
|
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]);
|
|
Result := TStaticType.Create(stOrdinal);
|
|
end;
|
|
else
|
|
raise ETypeException.Create('Unknown unary operator for type resolution.');
|
|
end;
|
|
end;
|
|
|
|
end.
|