AST Types

This commit is contained in:
Michael Schimmel
2025-10-26 09:58:42 +01:00
parent 85f2e02893
commit e379e6694c
7 changed files with 523 additions and 91 deletions
+68 -21
View File
@@ -97,9 +97,9 @@ type
// Defines the rules of the type system.
TTypeRules = record
private
class function Promote(const A, B: IStaticType): IStaticType; static;
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.
@@ -450,6 +450,10 @@ 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);
@@ -457,6 +461,9 @@ begin
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;
@@ -464,7 +471,13 @@ end;
class function TTypeRules.Promote(const A, B: IStaticType): IStaticType;
begin
// Numeric promotion rule: Float wins
// 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);
// 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
@@ -474,28 +487,54 @@ begin
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: 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]);
// 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 *always* results in Float, even Ordinal / Ordinal
if (Left.Kind in [stOrdinal, stFloat]) and (Right.Kind in [stOrdinal, stFloat]) then
Result := TTypes.Float
else
raise ETypeException.CreateFmt('Operator %s cannot be applied to %s and %s', [Op.ToString, Left.ToString, Right.ToString]);
// 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,
@@ -505,12 +544,12 @@ begin
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 := TTypes.Ordinal
else
raise ETypeException
.CreateFmt('Comparison operator %s cannot be applied to %s and %s', [Op.ToString, Left.ToString, Right.ToString]);
// 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.');
@@ -518,18 +557,26 @@ begin
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 (Right.Kind in [stOrdinal, stFloat]) then
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 (Right.Kind = stOrdinal) then
if not (rightKind = stOrdinal) then
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]);
Result := TTypes.Ordinal;
end;