Optional types and null propagation
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -17,7 +17,7 @@ uses
|
|||||||
|
|
||||||
type
|
type
|
||||||
IAstTypeChecker = interface(IAstVisitor)
|
IAstTypeChecker = interface(IAstVisitor)
|
||||||
function Execute(const RootNode: IAstNode; const Layout: IScopeLayout): IAstNode;
|
function Execute(const RootNode: IAstNode): IAstNode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TTypeChecker = class(TAstTransformer, IAstTypeChecker)
|
TTypeChecker = class(TAstTransformer, IAstTypeChecker)
|
||||||
@@ -30,7 +30,12 @@ type
|
|||||||
FSlotTypes: TArray<IStaticType>;
|
FSlotTypes: TArray<IStaticType>;
|
||||||
FUpvalueTypes: TArray<IStaticType>;
|
FUpvalueTypes: TArray<IStaticType>;
|
||||||
public
|
public
|
||||||
constructor Create(AParent: TTypeContext; ALayout: IScopeLayout; const AUpvalueTypes: TArray<IStaticType>);
|
constructor Create(
|
||||||
|
AParent: TTypeContext;
|
||||||
|
ALayout: IScopeLayout;
|
||||||
|
const AUpvalueTypes: TArray<IStaticType>;
|
||||||
|
ADescriptor: IScopeDescriptor
|
||||||
|
);
|
||||||
function LookupType(const Address: TResolvedAddress): IStaticType;
|
function LookupType(const Address: TResolvedAddress): IStaticType;
|
||||||
procedure SetType(SlotIndex: Integer; AType: IStaticType);
|
procedure SetType(SlotIndex: Integer; AType: IStaticType);
|
||||||
property Types: TArray<IStaticType> read FSlotTypes;
|
property Types: TArray<IStaticType> read FSlotTypes;
|
||||||
@@ -39,9 +44,14 @@ type
|
|||||||
private
|
private
|
||||||
FCurrentContext: TTypeContext;
|
FCurrentContext: TTypeContext;
|
||||||
FLog: ICompilerLog;
|
FLog: ICompilerLog;
|
||||||
|
FRootScope: IExecutionScope;
|
||||||
|
|
||||||
function CreateContextChain(L: IScopeLayout): TTypeContext;
|
function CreateContextChain(L: IScopeLayout): TTypeContext;
|
||||||
|
|
||||||
|
// Helpers for Null Propagation
|
||||||
|
function PrepareBaseType(const BaseNode: IAstNode; out IsOptional: Boolean): IStaticType;
|
||||||
|
function ApplyOptionality(const AType: IStaticType; IsOptional: Boolean): IStaticType;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
|
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
|
||||||
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
|
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
|
||||||
@@ -50,7 +60,7 @@ type
|
|||||||
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
|
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
|
||||||
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
|
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
|
||||||
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
|
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
|
||||||
function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override; // Replaced Ternary
|
function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override;
|
||||||
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
|
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
|
||||||
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
|
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
|
||||||
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
|
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
|
||||||
@@ -64,12 +74,17 @@ type
|
|||||||
function VisitKeyword(const Node: IKeywordNode): IAstNode; override;
|
function VisitKeyword(const Node: IKeywordNode): IAstNode; override;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(const RootLayout: IScopeLayout; const ALog: ICompilerLog);
|
constructor Create(const RootLayout: IScopeLayout; const RootScope: IExecutionScope; const ALog: ICompilerLog);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
function Execute(const RootNode: IAstNode; const Layout: IScopeLayout): IAstNode;
|
function Execute(const RootNode: IAstNode): IAstNode;
|
||||||
|
|
||||||
class function CheckTypes(const RootNode: IAstNode; const Layout: IScopeLayout; const ALog: ICompilerLog): IAstNode; static;
|
class function CheckTypes(
|
||||||
|
const RootNode: IAstNode;
|
||||||
|
const Layout: IScopeLayout;
|
||||||
|
const RootScope: IExecutionScope;
|
||||||
|
const ALog: ICompilerLog
|
||||||
|
): IAstNode; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -80,7 +95,14 @@ uses
|
|||||||
|
|
||||||
{ TTypeChecker.TTypeContext }
|
{ TTypeChecker.TTypeContext }
|
||||||
|
|
||||||
constructor TTypeChecker.TTypeContext.Create(AParent: TTypeContext; ALayout: IScopeLayout; const AUpvalueTypes: TArray<IStaticType>);
|
constructor TTypeChecker.TTypeContext.Create(
|
||||||
|
AParent: TTypeContext;
|
||||||
|
ALayout: IScopeLayout;
|
||||||
|
const AUpvalueTypes: TArray<IStaticType>;
|
||||||
|
ADescriptor: IScopeDescriptor
|
||||||
|
);
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FParent := AParent;
|
FParent := AParent;
|
||||||
@@ -90,8 +112,19 @@ begin
|
|||||||
if Assigned(FLayout) then
|
if Assigned(FLayout) then
|
||||||
begin
|
begin
|
||||||
SetLength(FSlotTypes, FLayout.SlotCount);
|
SetLength(FSlotTypes, FLayout.SlotCount);
|
||||||
for var i := 0 to High(FSlotTypes) do
|
|
||||||
FSlotTypes[i] := TTypes.Unknown;
|
if Assigned(ADescriptor) then
|
||||||
|
begin
|
||||||
|
// Load known types from descriptor (e.g. for Root Scope / RTL)
|
||||||
|
for i := 0 to High(FSlotTypes) do
|
||||||
|
FSlotTypes[i] := ADescriptor.GetSymbolType(i);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Initialize with Unknown for new scopes
|
||||||
|
for i := 0 to High(FSlotTypes) do
|
||||||
|
FSlotTypes[i] := TTypes.Unknown;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -142,21 +175,33 @@ end;
|
|||||||
function TTypeChecker.CreateContextChain(L: IScopeLayout): TTypeContext;
|
function TTypeChecker.CreateContextChain(L: IScopeLayout): TTypeContext;
|
||||||
var
|
var
|
||||||
p: TTypeContext;
|
p: TTypeContext;
|
||||||
|
desc: IScopeDescriptor;
|
||||||
begin
|
begin
|
||||||
if L = nil then
|
if L = nil then
|
||||||
exit(nil);
|
exit(nil);
|
||||||
|
|
||||||
p := CreateContextChain(L.Parent);
|
p := CreateContextChain(L.Parent);
|
||||||
Result := TTypeContext.Create(p, L, []);
|
|
||||||
|
desc := nil;
|
||||||
|
// Check if this layout is the root layout (has no parent).
|
||||||
|
// If so, attach the RootScope's descriptor to pull in RTL types (like +).
|
||||||
|
if (L.Parent = nil) and Assigned(FRootScope) then
|
||||||
|
begin
|
||||||
|
desc := FRootScope.Descriptor;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := TTypeContext.Create(p, L, [], desc);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TTypeChecker.Create(const RootLayout: IScopeLayout; const ALog: ICompilerLog);
|
constructor TTypeChecker.Create(const RootLayout: IScopeLayout; const RootScope: IExecutionScope; const ALog: ICompilerLog);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FLog := ALog;
|
FLog := ALog;
|
||||||
|
FRootScope := RootScope;
|
||||||
|
|
||||||
FCurrentContext := CreateContextChain(RootLayout);
|
FCurrentContext := CreateContextChain(RootLayout);
|
||||||
if FCurrentContext = nil then
|
if FCurrentContext = nil then
|
||||||
FCurrentContext := TTypeContext.Create(nil, nil, []);
|
FCurrentContext := TTypeContext.Create(nil, nil, [], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TTypeChecker.Destroy;
|
destructor TTypeChecker.Destroy;
|
||||||
@@ -170,19 +215,56 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypeChecker.CheckTypes(const RootNode: IAstNode; const Layout: IScopeLayout; const ALog: ICompilerLog): IAstNode;
|
class function TTypeChecker.CheckTypes(
|
||||||
|
const RootNode: IAstNode;
|
||||||
|
const Layout: IScopeLayout;
|
||||||
|
const RootScope: IExecutionScope;
|
||||||
|
const ALog: ICompilerLog
|
||||||
|
): IAstNode;
|
||||||
|
var
|
||||||
|
startLayout: IScopeLayout;
|
||||||
begin
|
begin
|
||||||
var checker := TTypeChecker.Create(Layout, ALog) as IAstTypeChecker;
|
// Determine the starting scope for the TypeChecker.
|
||||||
Result := checker.Execute(RootNode, Layout);
|
// The 'Layout' parameter represents the *inner* scope of the script (the "Room").
|
||||||
|
// To correctly simulate entering this scope (via VisitLambdaExpression), the TypeChecker
|
||||||
|
// must start in the *surrounding* scope (the "Hallway"), which is Layout.Parent.
|
||||||
|
if Assigned(Layout) then
|
||||||
|
startLayout := Layout.Parent
|
||||||
|
else
|
||||||
|
startLayout := nil;
|
||||||
|
|
||||||
|
var checker := TTypeChecker.Create(startLayout, RootScope, ALog) as IAstTypeChecker;
|
||||||
|
Result := checker.Execute(RootNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.Execute(const RootNode: IAstNode; const Layout: IScopeLayout): IAstNode;
|
function TTypeChecker.Execute(const RootNode: IAstNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([], nil);
|
Result := TAst.Block([], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// --- Helper ---
|
||||||
|
|
||||||
|
function TTypeChecker.PrepareBaseType(const BaseNode: IAstNode; out IsOptional: Boolean): IStaticType;
|
||||||
|
var
|
||||||
|
baseType: IStaticType;
|
||||||
|
begin
|
||||||
|
baseType := BaseNode.AsTypedNode.StaticType;
|
||||||
|
IsOptional := baseType.IsOptional;
|
||||||
|
Result := TTypes.Unwrap(baseType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTypeChecker.ApplyOptionality(const AType: IStaticType; IsOptional: Boolean): IStaticType;
|
||||||
|
begin
|
||||||
|
if IsOptional and (AType.Kind <> stUnknown) then
|
||||||
|
Result := TTypes.MakeOptional(AType)
|
||||||
|
else
|
||||||
|
Result := AType;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Visits ---
|
||||||
|
|
||||||
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode;
|
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Node;
|
Result := Node;
|
||||||
@@ -221,7 +303,6 @@ begin
|
|||||||
for i := 0 to Node.Arguments.Count - 1 do
|
for i := 0 to Node.Arguments.Count - 1 do
|
||||||
newArgs[i] := Accept(Node.Arguments[i]);
|
newArgs[i] := Accept(Node.Arguments[i]);
|
||||||
|
|
||||||
// Wrap in List
|
|
||||||
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
|
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
|
||||||
Result := TAst.Recur(Node.Identity, argList, TTypes.Void);
|
Result := TAst.Recur(Node.Identity, argList, TTypes.Void);
|
||||||
end;
|
end;
|
||||||
@@ -358,14 +439,40 @@ var
|
|||||||
finalDescriptor: IScopeDescriptor;
|
finalDescriptor: IScopeDescriptor;
|
||||||
paramIdent: IIdentifierNode;
|
paramIdent: IIdentifierNode;
|
||||||
paramIdentity: INamedIdentity;
|
paramIdentity: INamedIdentity;
|
||||||
|
lookupAddr: TResolvedAddress;
|
||||||
begin
|
begin
|
||||||
|
// 1. Resolve Upvalue Types
|
||||||
upvalueAddrs := Node.Upvalues;
|
upvalueAddrs := Node.Upvalues;
|
||||||
SetLength(upvalueTypes, Length(upvalueAddrs));
|
SetLength(upvalueTypes, Length(upvalueAddrs));
|
||||||
for i := 0 to High(upvalueAddrs) do
|
|
||||||
upvalueTypes[i] := FCurrentContext.LookupType(upvalueAddrs[i]);
|
|
||||||
|
|
||||||
FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes);
|
for i := 0 to High(upvalueAddrs) do
|
||||||
|
begin
|
||||||
|
lookupAddr := upvalueAddrs[i];
|
||||||
|
|
||||||
|
// CRITICAL: Scope Depth Adjustment
|
||||||
|
// The Binder calculated addresses relative to the *body* of this lambda (Internal View).
|
||||||
|
// FCurrentContext currently points to the *defining* scope (External View/Parent).
|
||||||
|
// Therefore, we must decrease the depth by 1 to look up the type in the current context.
|
||||||
|
if lookupAddr.Kind = akLocalOrParent then
|
||||||
|
begin
|
||||||
|
// Check > 0 to prevent crash if Binder somehow produced 0 (though logic says it shouldn't).
|
||||||
|
if lookupAddr.ScopeDepth > 0 then
|
||||||
|
Dec(lookupAddr.ScopeDepth)
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Guard for Binder quirks or edge cases
|
||||||
|
if Assigned(FLog) then
|
||||||
|
FLog.AddWarning('Compiler integrity check warning: Upvalue Depth 0 encountered during type check.', Node);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
upvalueTypes[i] := FCurrentContext.LookupType(lookupAddr);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 2. Enter New Scope (The "Room")
|
||||||
|
FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes, nil);
|
||||||
try
|
try
|
||||||
|
// 3. Register Parameters
|
||||||
SetLength(newParams, Node.Parameters.Count);
|
SetLength(newParams, Node.Parameters.Count);
|
||||||
SetLength(paramTypes, Node.Parameters.Count);
|
SetLength(paramTypes, Node.Parameters.Count);
|
||||||
|
|
||||||
@@ -383,21 +490,22 @@ begin
|
|||||||
newParams[i] := TAst.Identifier(paramIdentity, paramAdr, paramTypes[i]);
|
newParams[i] := TAst.Identifier(paramIdentity, paramAdr, paramTypes[i]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// 4. Visit Body
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
|
|
||||||
|
// 5. Determine Function Signature
|
||||||
bodyType := newBody.AsTypedNode.StaticType;
|
bodyType := newBody.AsTypedNode.StaticType;
|
||||||
methodType := TTypes.CreateMethod(paramTypes, bodyType);
|
methodType := TTypes.CreateMethod(paramTypes, bodyType);
|
||||||
|
|
||||||
FCurrentContext.SetType(0, methodType);
|
|
||||||
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
|
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
|
||||||
finally
|
finally
|
||||||
|
// 6. Leave Scope
|
||||||
var temp := FCurrentContext;
|
var temp := FCurrentContext;
|
||||||
FCurrentContext := FCurrentContext.FParent;
|
FCurrentContext := FCurrentContext.FParent;
|
||||||
temp.Free;
|
temp.Free;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Wrap in List
|
|
||||||
var paramList := TParameterList.Create(newParams, Node.Parameters.Identity);
|
var paramList := TParameterList.Create(newParams, Node.Parameters.Identity);
|
||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
TAst.LambdaExpr(
|
TAst.LambdaExpr(
|
||||||
Node.Identity,
|
Node.Identity,
|
||||||
@@ -493,9 +601,7 @@ begin
|
|||||||
retType := TTypes.Unknown;
|
retType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Wrap in List
|
|
||||||
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
|
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
|
||||||
|
|
||||||
Result := TAst.FunctionCall(Node.Identity, newCallee, argList, retType, Node.IsTailCall, nil, False);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, argList, retType, Node.IsTailCall, nil, False);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -514,7 +620,6 @@ begin
|
|||||||
else
|
else
|
||||||
blockType := TTypes.Void;
|
blockType := TTypes.Void;
|
||||||
|
|
||||||
// Wrap in List
|
|
||||||
var exprList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
|
var exprList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
|
||||||
Result := TAst.Block(Node.Identity, exprList, blockType);
|
Result := TAst.Block(Node.Identity, exprList, blockType);
|
||||||
end;
|
end;
|
||||||
@@ -561,11 +666,10 @@ var
|
|||||||
condType, branchType, resultType: IStaticType;
|
condType, branchType, resultType: IStaticType;
|
||||||
begin
|
begin
|
||||||
SetLength(newPairs, Length(Node.Pairs));
|
SetLength(newPairs, Length(Node.Pairs));
|
||||||
resultType := nil; // Start with nothing
|
resultType := nil;
|
||||||
|
|
||||||
for i := 0 to High(Node.Pairs) do
|
for i := 0 to High(Node.Pairs) do
|
||||||
begin
|
begin
|
||||||
// 1. Visit Condition
|
|
||||||
var newCond := Accept(Node.Pairs[i].Condition);
|
var newCond := Accept(Node.Pairs[i].Condition);
|
||||||
condType := newCond.AsTypedNode.StaticType;
|
condType := newCond.AsTypedNode.StaticType;
|
||||||
|
|
||||||
@@ -577,13 +681,11 @@ begin
|
|||||||
FLog.AddError(Format('Cond condition must be Boolean or Ordinal, but got %s', [condType.ToString]), Node);
|
FLog.AddError(Format('Cond condition must be Boolean or Ordinal, but got %s', [condType.ToString]), Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 2. Visit Branch
|
|
||||||
var newBranch := Accept(Node.Pairs[i].Branch);
|
var newBranch := Accept(Node.Pairs[i].Branch);
|
||||||
branchType := newBranch.AsTypedNode.StaticType;
|
branchType := newBranch.AsTypedNode.StaticType;
|
||||||
|
|
||||||
newPairs[i] := TCondPair.Create(newCond, newBranch);
|
newPairs[i] := TCondPair.Create(newCond, newBranch);
|
||||||
|
|
||||||
// 3. Promote Result Type
|
|
||||||
if resultType = nil then
|
if resultType = nil then
|
||||||
resultType := branchType
|
resultType := branchType
|
||||||
else
|
else
|
||||||
@@ -603,9 +705,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 4. Visit Else
|
|
||||||
newElse := Accept(Node.ElseBranch);
|
newElse := Accept(Node.ElseBranch);
|
||||||
// Else branch is mandatory in our new AST structure (can be Nop/Void, but is present)
|
|
||||||
var elseType := newElse.AsTypedNode.StaticType;
|
var elseType := newElse.AsTypedNode.StaticType;
|
||||||
|
|
||||||
if resultType = nil then
|
if resultType = nil then
|
||||||
@@ -631,43 +731,44 @@ end;
|
|||||||
|
|
||||||
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
||||||
var
|
var
|
||||||
baseType, elemType: IStaticType;
|
unwrappedBaseType, elemType: IStaticType;
|
||||||
fieldIndex: Integer;
|
fieldIndex: Integer;
|
||||||
newBase, newMember: IAstNode;
|
newBase, newMember: IAstNode;
|
||||||
|
isOptionalAccess: Boolean;
|
||||||
begin
|
begin
|
||||||
newBase := Accept(Node.Base);
|
newBase := Accept(Node.Base);
|
||||||
newMember := Accept(Node.Member);
|
newMember := Accept(Node.Member);
|
||||||
|
|
||||||
baseType := newBase.AsTypedNode.StaticType;
|
unwrappedBaseType := PrepareBaseType(newBase, isOptionalAccess);
|
||||||
elemType := TTypes.Unknown;
|
elemType := TTypes.Unknown;
|
||||||
|
|
||||||
if (baseType.Kind <> TStaticTypeKind.stUnknown) then
|
if (unwrappedBaseType.Kind <> TStaticTypeKind.stUnknown) then
|
||||||
begin
|
begin
|
||||||
if (baseType.Kind = TStaticTypeKind.stRecord) or (baseType.Kind = TStaticTypeKind.stRecordSeries) then
|
if (unwrappedBaseType.Kind = TStaticTypeKind.stRecord) or (unwrappedBaseType.Kind = TStaticTypeKind.stRecordSeries) then
|
||||||
begin
|
begin
|
||||||
fieldIndex := baseType.Definition.IndexOf(Node.Member.Value);
|
fieldIndex := unwrappedBaseType.Definition.IndexOf(Node.Member.Value);
|
||||||
if fieldIndex < 0 then
|
if fieldIndex < 0 then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Member "%s" not found in type %s', [Node.Member.Value.Name, baseType.ToString]), Node);
|
FLog.AddError(Format('Member "%s" not found in type %s', [Node.Member.Value.Name, unwrappedBaseType.ToString]), Node);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
var fieldType := TTypes.FromScalarKind(baseType.Definition[fieldIndex].Value);
|
var fieldType := TTypes.FromScalarKind(unwrappedBaseType.Definition[fieldIndex].Value);
|
||||||
if baseType.Kind = TStaticTypeKind.stRecord then
|
if unwrappedBaseType.Kind = TStaticTypeKind.stRecord then
|
||||||
elemType := fieldType
|
elemType := fieldType
|
||||||
else
|
else
|
||||||
elemType := TTypes.CreateSeries(fieldType);
|
elemType := TTypes.CreateSeries(fieldType);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if (baseType.Kind = TStaticTypeKind.stGenericRecord) then
|
else if (unwrappedBaseType.Kind = TStaticTypeKind.stGenericRecord) then
|
||||||
begin
|
begin
|
||||||
var genDef := baseType.GenericDefinition;
|
var genDef := unwrappedBaseType.GenericDefinition;
|
||||||
fieldIndex := genDef.IndexOf(Node.Member.Value);
|
fieldIndex := genDef.IndexOf(Node.Member.Value);
|
||||||
if fieldIndex < 0 then
|
if fieldIndex < 0 then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Member "%s" not found in type %s', [Node.Member.Value.Name, baseType.ToString]), Node);
|
FLog.AddError(Format('Member "%s" not found in type %s', [Node.Member.Value.Name, unwrappedBaseType.ToString]), Node);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
elemType := genDef[fieldIndex].Value;
|
elemType := genDef[fieldIndex].Value;
|
||||||
@@ -675,38 +776,42 @@ begin
|
|||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Member access requires a record type, but got %s', [baseType.ToString]), Node);
|
FLog.AddError(Format('Member access requires a record type, but got %s', [unwrappedBaseType.ToString]), Node);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
elemType := ApplyOptionality(elemType, isOptionalAccess);
|
||||||
|
|
||||||
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, elemType);
|
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, elemType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
||||||
var
|
var
|
||||||
baseType, indexType, elemType: IStaticType;
|
unwrappedBaseType, indexType, elemType: IStaticType;
|
||||||
newBase, newIndex: IAstNode;
|
newBase, newIndex: IAstNode;
|
||||||
|
isOptionalAccess: Boolean;
|
||||||
begin
|
begin
|
||||||
newBase := Accept(Node.Base);
|
newBase := Accept(Node.Base);
|
||||||
newIndex := Accept(Node.Index);
|
newIndex := Accept(Node.Index);
|
||||||
|
|
||||||
baseType := newBase.AsTypedNode.StaticType;
|
unwrappedBaseType := PrepareBaseType(newBase, isOptionalAccess);
|
||||||
|
|
||||||
indexType := newIndex.AsTypedNode.StaticType;
|
indexType := newIndex.AsTypedNode.StaticType;
|
||||||
elemType := TTypes.Unknown;
|
elemType := TTypes.Unknown;
|
||||||
|
|
||||||
if (baseType.Kind <> TStaticTypeKind.stUnknown) then
|
if (unwrappedBaseType.Kind <> TStaticTypeKind.stUnknown) then
|
||||||
begin
|
begin
|
||||||
if (baseType.Kind <> TStaticTypeKind.stSeries) and (baseType.Kind <> TStaticTypeKind.stRecordSeries) then
|
if (unwrappedBaseType.Kind <> TStaticTypeKind.stSeries) and (unwrappedBaseType.Kind <> TStaticTypeKind.stRecordSeries) then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Indexer `[]` can only be applied to series types, but got %s', [baseType.ToString]), Node);
|
FLog.AddError(Format('Indexer `[]` can only be applied to series types, but got %s', [unwrappedBaseType.ToString]), Node);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if baseType.Kind = TStaticTypeKind.stSeries then
|
if unwrappedBaseType.Kind = TStaticTypeKind.stSeries then
|
||||||
elemType := baseType.ElementType
|
elemType := unwrappedBaseType.ElementType
|
||||||
else
|
else
|
||||||
elemType := TTypes.CreateRecord(baseType.Definition);
|
elemType := TTypes.CreateRecord(unwrappedBaseType.Definition);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -716,6 +821,8 @@ begin
|
|||||||
FLog.AddError(Format('Indexer `[]` requires an Ordinal index, but got %s', [indexType.ToString]), Node);
|
FLog.AddError(Format('Indexer `[]` requires an Ordinal index, but got %s', [indexType.ToString]), Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
elemType := ApplyOptionality(elemType, isOptionalAccess);
|
||||||
|
|
||||||
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
|
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -770,7 +877,6 @@ begin
|
|||||||
scalarDefFields[i] := TScalarRecordField.Create(newFields[i].Key.Value, scalarKind);
|
scalarDefFields[i] := TScalarRecordField.Create(newFields[i].Key.Value, scalarKind);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Wrap in List
|
|
||||||
var fieldList := TRecordFieldList.Create(newFields, Node.Fields.Identity);
|
var fieldList := TRecordFieldList.Create(newFields, Node.Fields.Identity);
|
||||||
|
|
||||||
if allScalar then
|
if allScalar then
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ begin
|
|||||||
var boundAst := TAstBinder.Bind(FRootScope.Descriptor.Layout, Node, Layout, Log, FFunctionRegistry, ArgTypes);
|
var boundAst := TAstBinder.Bind(FRootScope.Descriptor.Layout, Node, Layout, Log, FFunctionRegistry, ArgTypes);
|
||||||
|
|
||||||
// 2. Check Types (produces Typed AST with Descriptors baked into nodes)
|
// 2. Check Types (produces Typed AST with Descriptors baked into nodes)
|
||||||
Result := TTypeChecker.CheckTypes(boundAst, Layout, Log);
|
Result := TTypeChecker.CheckTypes(boundAst, Layout, FRootScope, Log);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TEnvironment.GetMacroRegistry: IMacroRegistry;
|
function TEnvironment.GetMacroRegistry: IMacroRegistry;
|
||||||
|
|||||||
@@ -413,6 +413,10 @@ var
|
|||||||
scalarValue: TScalar;
|
scalarValue: TScalar;
|
||||||
begin
|
begin
|
||||||
baseValue := Node.Base.Accept(Self);
|
baseValue := Node.Base.Accept(Self);
|
||||||
|
|
||||||
|
if baseValue.IsVoid then
|
||||||
|
exit(TDataValue.Void);
|
||||||
|
|
||||||
indexValue := Node.Index.Accept(Self);
|
indexValue := Node.Index.Accept(Self);
|
||||||
|
|
||||||
if (indexValue.Kind <> vkScalar) or (indexValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
|
if (indexValue.Kind <> vkScalar) or (indexValue.AsScalar.Kind <> TScalar.TKind.Ordinal) then
|
||||||
@@ -461,6 +465,9 @@ var
|
|||||||
begin
|
begin
|
||||||
baseValue := Node.Base.Accept(Self);
|
baseValue := Node.Base.Accept(Self);
|
||||||
|
|
||||||
|
if baseValue.IsVoid then
|
||||||
|
exit(TDataValue.Void);
|
||||||
|
|
||||||
case baseValue.Kind of
|
case baseValue.Kind of
|
||||||
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.Fields[Node.Member.Value]);
|
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.Fields[Node.Member.Value]);
|
||||||
vkScalarRecord: Result := TDataValue(baseValue.AsScalarRecord.Fields[Node.Member.Value]);
|
vkScalarRecord: Result := TDataValue(baseValue.AsScalarRecord.Fields[Node.Member.Value]);
|
||||||
|
|||||||
@@ -639,10 +639,9 @@ begin
|
|||||||
else if SameText(head.Token.Text, '?') then
|
else if SameText(head.Token.Text, '?') then
|
||||||
begin
|
begin
|
||||||
// (? test1 branch1 test2 branch2 ... else)
|
// (? test1 branch1 test2 branch2 ... else)
|
||||||
// Requires odd number of tailNodes (pairs + 1 else)
|
|
||||||
var count := Length(tailNodes);
|
var count := Length(tailNodes);
|
||||||
if (count < 1) or ((count mod 2) = 0) then
|
if (count < 1) or ((count mod 2) = 0) then
|
||||||
Error('Syntax Error: ''?'' (cond) requires an odd number of arguments (pairs of condition/branch and one final else).');
|
Error('Syntax Error: ''?'' (cond) requires an odd number of arguments.');
|
||||||
|
|
||||||
var pairs: TArray<TCondPair>;
|
var pairs: TArray<TCondPair>;
|
||||||
SetLength(pairs, count div 2);
|
SetLength(pairs, count div 2);
|
||||||
@@ -667,7 +666,7 @@ begin
|
|||||||
Error('Syntax Error: ''defmacro'' requires a name, a parameter list, and a body.');
|
Error('Syntax Error: ''defmacro'' requires a name, a parameter list, and a body.');
|
||||||
|
|
||||||
var macroName := tailNodes[0].AsIdentifier;
|
var macroName := tailNodes[0].AsIdentifier;
|
||||||
var macroParams := elements[2].Params;
|
var macroParams := elements[2].Params; // Parameter list parsing happens in ParseExpression for brackets
|
||||||
|
|
||||||
if tailNodes[2].Kind <> akQuasiquote then
|
if tailNodes[2].Kind <> akQuasiquote then
|
||||||
Error('Syntax Error: Expected a quasiquote as macro body.');
|
Error('Syntax Error: Expected a quasiquote as macro body.');
|
||||||
@@ -677,25 +676,68 @@ begin
|
|||||||
else if SameText(head.Token.Text, 'assign') then
|
else if SameText(head.Token.Text, 'assign') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''assign'' requires exactly 2 arguments (target and value).');
|
Error('Syntax Error: ''assign'' requires exactly 2 arguments.');
|
||||||
Result := TAst.Assign(tailNodes[0], tailNodes[1], startLoc);
|
Result := TAst.Assign(tailNodes[0], tailNodes[1], startLoc);
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'fn') then
|
else if SameText(head.Token.Text, 'fn') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''fn'' requires a parameter list and a body.');
|
Error('Syntax Error: ''fn'' requires a parameter list and a body.');
|
||||||
|
// elements[1] is the parameter list node which contains the parsed Params array
|
||||||
Result := TAst.LambdaExpr(elements[1].Params, tailNodes[1], startLoc);
|
Result := TAst.LambdaExpr(elements[1].Params, tailNodes[1], startLoc);
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'do') then
|
else if SameText(head.Token.Text, 'do') then
|
||||||
|
begin
|
||||||
Result := TAst.Block(tailNodes, startLoc)
|
Result := TAst.Block(tailNodes, startLoc)
|
||||||
|
end
|
||||||
else if SameText(head.Token.Text, 'recur') then
|
else if SameText(head.Token.Text, 'recur') then
|
||||||
|
begin
|
||||||
Result := TAst.Recur(tailNodes, startLoc)
|
Result := TAst.Recur(tailNodes, startLoc)
|
||||||
|
end
|
||||||
else if SameText(head.Token.Text, 'get') then
|
else if SameText(head.Token.Text, 'get') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''get'' requires exactly 2 arguments (base and index).');
|
Error('Syntax Error: ''get'' requires exactly 2 arguments (base and index).');
|
||||||
Result := TAst.Indexer(tailNodes[0], tailNodes[1], startLoc)
|
Result := TAst.Indexer(tailNodes[0], tailNodes[1], startLoc)
|
||||||
end
|
end
|
||||||
|
// --- NEW: Support for new-series ---
|
||||||
|
else if SameText(head.Token.Text, 'new-series') then
|
||||||
|
begin
|
||||||
|
if Length(tailNodes) <> 1 then
|
||||||
|
Error('Syntax Error: ''new-series'' requires exactly 1 argument (definition string).');
|
||||||
|
|
||||||
|
// Extract definition string from constant node
|
||||||
|
if (tailNodes[0].Kind = akConstant) and (tailNodes[0].AsConstant.Value.Kind = vkText) then
|
||||||
|
Result := TAst.CreateSeries(tailNodes[0].AsConstant.Value.AsText, startLoc)
|
||||||
|
else
|
||||||
|
Error('Syntax Error: ''new-series'' argument must be a string literal.');
|
||||||
|
end
|
||||||
|
// --- NEW: Support for add-item ---
|
||||||
|
else if SameText(head.Token.Text, 'add-item') then
|
||||||
|
begin
|
||||||
|
if not (Length(tailNodes) in [2, 3]) then
|
||||||
|
Error('Syntax Error: ''add-item'' requires 2 or 3 arguments (series, value, [lookback]).');
|
||||||
|
|
||||||
|
var lookback: IAstNode := nil;
|
||||||
|
if Length(tailNodes) = 3 then
|
||||||
|
lookback := tailNodes[2];
|
||||||
|
|
||||||
|
if tailNodes[0].Kind <> akIdentifier then
|
||||||
|
Error('Syntax Error: ''add-item'' first argument must be a series identifier.');
|
||||||
|
|
||||||
|
Result := TAst.AddSeriesItem(tailNodes[0].AsIdentifier, tailNodes[1], lookback, startLoc);
|
||||||
|
end
|
||||||
|
// --- NEW: Support for count (SeriesLength) ---
|
||||||
|
else if SameText(head.Token.Text, 'count') then
|
||||||
|
begin
|
||||||
|
if Length(tailNodes) <> 1 then
|
||||||
|
Error('Syntax Error: ''count'' requires exactly 1 argument (series).');
|
||||||
|
|
||||||
|
if tailNodes[0].Kind <> akIdentifier then
|
||||||
|
Error('Syntax Error: ''count'' argument must be a series identifier.');
|
||||||
|
|
||||||
|
Result := TAst.SeriesLength(tailNodes[0].AsIdentifier, startLoc);
|
||||||
|
end
|
||||||
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
|
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 1 then
|
if Length(tailNodes) <> 1 then
|
||||||
@@ -1136,8 +1178,6 @@ class function TAstScript.Parse(const ASource: string): IAstNode;
|
|||||||
var
|
var
|
||||||
p: TParser;
|
p: TParser;
|
||||||
begin
|
begin
|
||||||
if ASource.Trim.IsEmpty then
|
|
||||||
exit(nil);
|
|
||||||
p := TParser.Create(ASource);
|
p := TParser.Create(ASource);
|
||||||
try
|
try
|
||||||
Result := p.Parse;
|
Result := p.Parse;
|
||||||
|
|||||||
+460
-127
@@ -5,18 +5,18 @@ interface
|
|||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
|
System.Generics.Defaults,
|
||||||
Myc.Data.Scalar,
|
Myc.Data.Scalar,
|
||||||
Myc.Data.Keyword;
|
Myc.Data.Keyword;
|
||||||
|
|
||||||
type
|
type
|
||||||
IStaticType = interface;
|
IStaticType = interface;
|
||||||
|
|
||||||
// Defines the categories of types available in the language.
|
|
||||||
TStaticTypeKind = (
|
TStaticTypeKind = (
|
||||||
stUnknown, // Used during inference before a type is known
|
stUnknown,
|
||||||
stVoid,
|
stVoid,
|
||||||
stOrdinal, // Int64
|
stOrdinal,
|
||||||
stFloat, // Double
|
stFloat,
|
||||||
stBoolean,
|
stBoolean,
|
||||||
stDateTime,
|
stDateTime,
|
||||||
stText,
|
stText,
|
||||||
@@ -33,7 +33,6 @@ type
|
|||||||
function ToString: string;
|
function ToString: string;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Defines the signature of a method
|
|
||||||
IMethodSignature = interface
|
IMethodSignature = interface
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetParamTypes: TArray<IStaticType>;
|
function GetParamTypes: TArray<IStaticType>;
|
||||||
@@ -44,40 +43,32 @@ type
|
|||||||
function GetHashCode: Integer;
|
function GetHashCode: Integer;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Defines a mapping for generic record fields (Key -> StaticType)
|
|
||||||
IGenericRecordDefinition = IKeywordMapping<IStaticType>;
|
IGenericRecordDefinition = IKeywordMapping<IStaticType>;
|
||||||
TGenericRecordRegistry = TKeywordMappingRegistry<IStaticType>;
|
TGenericRecordRegistry = TKeywordMappingRegistry<IStaticType>;
|
||||||
|
|
||||||
// Represents a complete static type definition.
|
|
||||||
IStaticType = interface
|
IStaticType = interface
|
||||||
{$region 'private'}
|
{$region 'private'}
|
||||||
function GetKind: TStaticTypeKind;
|
function GetKind: TStaticTypeKind;
|
||||||
|
function GetIsOptional: Boolean;
|
||||||
function GetElementType: IStaticType;
|
function GetElementType: IStaticType;
|
||||||
function GetSignatures: TArray<IMethodSignature>;
|
function GetSignatures: TArray<IMethodSignature>;
|
||||||
function GetDefinition: IScalarRecordDefinition;
|
function GetDefinition: IScalarRecordDefinition;
|
||||||
function GetGenericDefinition: IGenericRecordDefinition;
|
function GetGenericDefinition: IGenericRecordDefinition;
|
||||||
{$endregion}
|
{$endregion}
|
||||||
|
|
||||||
// The kind of type (e.g., Ordinal, Series, etc.)
|
|
||||||
property Kind: TStaticTypeKind read GetKind;
|
property Kind: TStaticTypeKind read GetKind;
|
||||||
// The element type (if Kind = stSeries)
|
property IsOptional: Boolean read GetIsOptional;
|
||||||
property ElementType: IStaticType read GetElementType;
|
property ElementType: IStaticType read GetElementType;
|
||||||
|
|
||||||
// The signatures (if Kind = stMethod).
|
|
||||||
property Signatures: TArray<IMethodSignature> read GetSignatures;
|
property Signatures: TArray<IMethodSignature> read GetSignatures;
|
||||||
|
|
||||||
// The definition (if Kind = stRecord or stRecordSeries)
|
|
||||||
property Definition: IScalarRecordDefinition read GetDefinition;
|
property Definition: IScalarRecordDefinition read GetDefinition;
|
||||||
// The definition (if Kind = stGenericRecord)
|
|
||||||
property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition;
|
property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition;
|
||||||
|
|
||||||
// Checks for type equality
|
|
||||||
function IsEqual(const Other: IStaticType): Boolean;
|
function IsEqual(const Other: IStaticType): Boolean;
|
||||||
|
function IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
function ToString: string;
|
function ToString: string;
|
||||||
function GetHashCode: Integer;
|
function GetHashCode: Integer;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Factory and Flyweight access for static types.
|
|
||||||
TTypes = record
|
TTypes = record
|
||||||
private
|
private
|
||||||
class var
|
class var
|
||||||
@@ -97,9 +88,35 @@ type
|
|||||||
class var
|
class var
|
||||||
FKeyword: IStaticType;
|
FKeyword: IStaticType;
|
||||||
|
|
||||||
|
class var
|
||||||
|
FOrdinalOpt: IStaticType;
|
||||||
|
class var
|
||||||
|
FFloatOpt: IStaticType;
|
||||||
|
class var
|
||||||
|
FBooleanOpt: IStaticType;
|
||||||
|
class var
|
||||||
|
FDateTimeOpt: IStaticType;
|
||||||
|
class var
|
||||||
|
FTextOpt: IStaticType;
|
||||||
|
class var
|
||||||
|
FKeywordOpt: IStaticType;
|
||||||
|
|
||||||
|
class var
|
||||||
|
FSeriesCache: TDictionary<IStaticType, IStaticType>;
|
||||||
|
class var
|
||||||
|
FRecordCache: TDictionary<IScalarRecordDefinition, IStaticType>;
|
||||||
|
class var
|
||||||
|
FRecordSeriesCache: TDictionary<IScalarRecordDefinition, IStaticType>;
|
||||||
|
class var
|
||||||
|
FGenericRecordCache: TDictionary<IGenericRecordDefinition, IStaticType>;
|
||||||
|
class var
|
||||||
|
FMethodCache: TDictionary<TArray<IMethodSignature>, IStaticType>;
|
||||||
|
class var
|
||||||
|
FOptionalCache: TDictionary<IStaticType, IStaticType>;
|
||||||
|
|
||||||
class constructor Create;
|
class constructor Create;
|
||||||
|
class destructor Destroy;
|
||||||
public
|
public
|
||||||
// Flyweight accessors
|
|
||||||
class property Unknown: IStaticType read FUnknown;
|
class property Unknown: IStaticType read FUnknown;
|
||||||
class property Void: IStaticType read FVoid;
|
class property Void: IStaticType read FVoid;
|
||||||
class property Ordinal: IStaticType read FOrdinal;
|
class property Ordinal: IStaticType read FOrdinal;
|
||||||
@@ -109,7 +126,13 @@ type
|
|||||||
class property Text: IStaticType read FText;
|
class property Text: IStaticType read FText;
|
||||||
class property Keyword: IStaticType read FKeyword;
|
class property Keyword: IStaticType read FKeyword;
|
||||||
|
|
||||||
// Factory functions
|
class property OrdinalOpt: IStaticType read FOrdinalOpt;
|
||||||
|
class property FloatOpt: IStaticType read FFloatOpt;
|
||||||
|
class property BooleanOpt: IStaticType read FBooleanOpt;
|
||||||
|
class property DateTimeOpt: IStaticType read FDateTimeOpt;
|
||||||
|
class property TextOpt: IStaticType read FTextOpt;
|
||||||
|
class property KeywordOpt: IStaticType read FKeywordOpt;
|
||||||
|
|
||||||
class function CreateSeries(const AElementType: IStaticType): IStaticType; static;
|
class function CreateSeries(const AElementType: IStaticType): IStaticType; static;
|
||||||
class function CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType; static;
|
class function CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType; static;
|
||||||
class function CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType; static;
|
class function CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType; static;
|
||||||
@@ -117,22 +140,16 @@ type
|
|||||||
class function CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType; static;
|
class function CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType; static;
|
||||||
class function CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType; static;
|
class function CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType; static;
|
||||||
|
|
||||||
|
class function MakeOptional(const AType: IStaticType): IStaticType; static;
|
||||||
|
class function Unwrap(const AType: IStaticType): IStaticType; static;
|
||||||
class function FromScalarKind(AKind: TScalar.TKind): IStaticType; static;
|
class function FromScalarKind(AKind: TScalar.TKind): IStaticType; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Defines the rules of the type system.
|
|
||||||
TTypeRules = record
|
TTypeRules = record
|
||||||
public
|
public
|
||||||
// Returns the common type (promotion) or nil if incompatible.
|
|
||||||
class function Promote(const A, B: IStaticType): IStaticType; static;
|
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;
|
class function CanAssign(const Target, Source: IStaticType): Boolean; static;
|
||||||
|
|
||||||
// Determines the result type of a binary operation. Returns nil on failure.
|
|
||||||
class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static;
|
class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static;
|
||||||
|
|
||||||
// Determines the result type of a unary operation. Returns nil on failure.
|
|
||||||
class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static;
|
class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -150,8 +167,7 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.Generics.Defaults,
|
System.Hash; // Critical for robust hash calculations
|
||||||
System.Hash;
|
|
||||||
|
|
||||||
{ TStaticTypeKindHelper }
|
{ TStaticTypeKindHelper }
|
||||||
|
|
||||||
@@ -182,15 +198,22 @@ type
|
|||||||
TAbstractStaticType = class(TInterfacedObject, IStaticType)
|
TAbstractStaticType = class(TInterfacedObject, IStaticType)
|
||||||
protected
|
protected
|
||||||
function GetKind: TStaticTypeKind; virtual; abstract;
|
function GetKind: TStaticTypeKind; virtual; abstract;
|
||||||
|
function GetIsOptional: Boolean; virtual;
|
||||||
function GetElementType: IStaticType; virtual;
|
function GetElementType: IStaticType; virtual;
|
||||||
function GetSignatures: TArray<IMethodSignature>; virtual;
|
function GetSignatures: TArray<IMethodSignature>; virtual;
|
||||||
function GetDefinition: IScalarRecordDefinition; virtual;
|
function GetDefinition: IScalarRecordDefinition; virtual;
|
||||||
function GetGenericDefinition: IGenericRecordDefinition; virtual;
|
function GetGenericDefinition: IGenericRecordDefinition; virtual;
|
||||||
function IsEqual(const Other: IStaticType): Boolean; virtual;
|
function IsEqual(const Other: IStaticType): Boolean; virtual;
|
||||||
|
function IsStructurallyEqual(const Other: IStaticType): Boolean; virtual;
|
||||||
function GetHashCode: Integer; override; abstract;
|
function GetHashCode: Integer; override; abstract;
|
||||||
function ToString: string; override;
|
function ToString: string; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TAbstractStaticType.GetIsOptional: Boolean;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
function TAbstractStaticType.GetElementType: IStaticType;
|
function TAbstractStaticType.GetElementType: IStaticType;
|
||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
@@ -211,11 +234,20 @@ begin
|
|||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TAbstractStaticType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
|
begin
|
||||||
|
if not Assigned(Other) then
|
||||||
|
exit(False);
|
||||||
|
Result := (GetKind = TTypes.Unwrap(Other).Kind);
|
||||||
|
end;
|
||||||
|
|
||||||
function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean;
|
function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean;
|
||||||
begin
|
begin
|
||||||
if not Assigned(Other) then
|
if not Assigned(Other) then
|
||||||
exit(False);
|
exit(False);
|
||||||
Result := (GetKind = Other.Kind);
|
if GetIsOptional <> Other.IsOptional then
|
||||||
|
exit(False);
|
||||||
|
Result := IsStructurallyEqual(Other);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAbstractStaticType.ToString: string;
|
function TAbstractStaticType.ToString: string;
|
||||||
@@ -223,6 +255,93 @@ begin
|
|||||||
Result := GetKind.ToString;
|
Result := GetKind.ToString;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// --- Optional Type Decorator ---
|
||||||
|
|
||||||
|
type
|
||||||
|
TOptionalType = class(TInterfacedObject, IStaticType)
|
||||||
|
private
|
||||||
|
FInner: IStaticType;
|
||||||
|
public
|
||||||
|
constructor Create(const AInner: IStaticType);
|
||||||
|
function GetKind: TStaticTypeKind;
|
||||||
|
function GetIsOptional: Boolean;
|
||||||
|
function GetElementType: IStaticType;
|
||||||
|
function GetSignatures: TArray<IMethodSignature>;
|
||||||
|
function GetDefinition: IScalarRecordDefinition;
|
||||||
|
function GetGenericDefinition: IGenericRecordDefinition;
|
||||||
|
function IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
|
function IsEqual(const Other: IStaticType): Boolean;
|
||||||
|
function GetHashCode: Integer; override;
|
||||||
|
function ToString: string; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TOptionalType.Create(const AInner: IStaticType);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FInner := AInner;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetKind: TStaticTypeKind;
|
||||||
|
begin
|
||||||
|
Result := FInner.Kind;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetIsOptional: Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetElementType: IStaticType;
|
||||||
|
begin
|
||||||
|
Result := FInner.ElementType;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetSignatures: TArray<IMethodSignature>;
|
||||||
|
begin
|
||||||
|
Result := FInner.Signatures;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetDefinition: IScalarRecordDefinition;
|
||||||
|
begin
|
||||||
|
Result := FInner.Definition;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetGenericDefinition: IGenericRecordDefinition;
|
||||||
|
begin
|
||||||
|
Result := FInner.GenericDefinition;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
|
begin
|
||||||
|
Result := FInner.IsStructurallyEqual(TTypes.Unwrap(Other));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.IsEqual(const Other: IStaticType): Boolean;
|
||||||
|
begin
|
||||||
|
if not Assigned(Other) then
|
||||||
|
exit(False);
|
||||||
|
if not Other.IsOptional then
|
||||||
|
exit(False);
|
||||||
|
Result := IsStructurallyEqual(Other);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.GetHashCode: Integer;
|
||||||
|
var
|
||||||
|
h: Integer;
|
||||||
|
optionalMarker: Integer;
|
||||||
|
begin
|
||||||
|
// We mix the Inner Hash with a constant marker using BobJenkins to ensure
|
||||||
|
// T and T? have distinct, uncorrelated hashes.
|
||||||
|
h := FInner.GetHashCode;
|
||||||
|
optionalMarker := $11111111; // Arbitrary constant to signal "Optionality"
|
||||||
|
Result := THashBobJenkins.GetHashValue(optionalMarker, SizeOf(Integer), h);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOptionalType.ToString: string;
|
||||||
|
begin
|
||||||
|
Result := FInner.ToString + '?';
|
||||||
|
end;
|
||||||
|
|
||||||
// --- Simple (Flyweight) Type Implementations ---
|
// --- Simple (Flyweight) Type Implementations ---
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -248,7 +367,7 @@ end;
|
|||||||
|
|
||||||
function TSimpleStaticType.GetHashCode: Integer;
|
function TSimpleStaticType.GetHashCode: Integer;
|
||||||
begin
|
begin
|
||||||
Result := Ord(FKind);
|
Result := THashBobJenkins.GetHashValue(FKind, SizeOf(TStaticTypeKind), 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Complex Type Implementations ---
|
// --- Complex Type Implementations ---
|
||||||
@@ -261,7 +380,7 @@ type
|
|||||||
constructor Create(AElementType: IStaticType);
|
constructor Create(AElementType: IStaticType);
|
||||||
function GetKind: TStaticTypeKind; override;
|
function GetKind: TStaticTypeKind; override;
|
||||||
function GetElementType: IStaticType; override;
|
function GetElementType: IStaticType; override;
|
||||||
function IsEqual(const Other: IStaticType): Boolean; override;
|
function IsStructurallyEqual(const Other: IStaticType): Boolean; override;
|
||||||
function GetHashCode: Integer; override;
|
function GetHashCode: Integer; override;
|
||||||
function ToString: string; override;
|
function ToString: string; override;
|
||||||
end;
|
end;
|
||||||
@@ -282,18 +401,24 @@ begin
|
|||||||
Result := FElementType;
|
Result := FElementType;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TSeriesType.IsEqual(const Other: IStaticType): Boolean;
|
function TSeriesType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (Assigned(Other)) and (Other.Kind = stSeries) and (Self.FElementType.IsEqual(Other.ElementType));
|
var uOther := TTypes.Unwrap(Other);
|
||||||
|
Result := (uOther.Kind = stSeries) and (Self.FElementType.IsEqual(uOther.ElementType));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TSeriesType.GetHashCode: Integer;
|
function TSeriesType.GetHashCode: Integer;
|
||||||
|
var
|
||||||
|
h: Integer;
|
||||||
begin
|
begin
|
||||||
Result := Ord(stSeries);
|
// Seed with Kind, then mix in ElementType's hash
|
||||||
|
h := Ord(stSeries);
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(TStaticTypeKind), 0);
|
||||||
|
|
||||||
if Assigned(FElementType) then
|
if Assigned(FElementType) then
|
||||||
begin
|
begin
|
||||||
var hash := FElementType.GetHashCode;
|
h := FElementType.GetHashCode;
|
||||||
Result := THashBobJenkins.GetHashValue(Hash, SizeOf(Integer), Result);
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -323,18 +448,23 @@ end;
|
|||||||
|
|
||||||
function TMethodSignature.GetHashCode: Integer;
|
function TMethodSignature.GetHashCode: Integer;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i, h: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Start with 0 (or a signature constant), mix ReturnType, then Params
|
||||||
Result := 0;
|
Result := 0;
|
||||||
|
|
||||||
if Assigned(FReturnType) then
|
if Assigned(FReturnType) then
|
||||||
Result := FReturnType.GetHashCode;
|
begin
|
||||||
|
h := FReturnType.GetHashCode;
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
|
end;
|
||||||
|
|
||||||
for i := 0 to High(FParamTypes) do
|
for i := 0 to High(FParamTypes) do
|
||||||
begin
|
begin
|
||||||
if Assigned(FParamTypes[i]) then
|
if Assigned(FParamTypes[i]) then
|
||||||
begin
|
begin
|
||||||
var hash := FParamTypes[i].GetHashCode;
|
h := FParamTypes[i].GetHashCode;
|
||||||
Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result);
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -349,7 +479,7 @@ type
|
|||||||
constructor Create(const ASignatures: TArray<IMethodSignature>);
|
constructor Create(const ASignatures: TArray<IMethodSignature>);
|
||||||
function GetKind: TStaticTypeKind; override;
|
function GetKind: TStaticTypeKind; override;
|
||||||
function GetSignatures: TArray<IMethodSignature>; override;
|
function GetSignatures: TArray<IMethodSignature>; override;
|
||||||
function IsEqual(const Other: IStaticType): Boolean; override;
|
function IsStructurallyEqual(const Other: IStaticType): Boolean; override;
|
||||||
function GetHashCode: Integer; override;
|
function GetHashCode: Integer; override;
|
||||||
function ToString: string; override;
|
function ToString: string; override;
|
||||||
end;
|
end;
|
||||||
@@ -386,14 +516,15 @@ begin
|
|||||||
Result := Format('Method(%s): %s', [paramStr, Sig.ReturnType.ToString]);
|
Result := Format('Method(%s): %s', [paramStr, Sig.ReturnType.ToString]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMethodType.IsEqual(const Other: IStaticType): Boolean;
|
function TMethodType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
var
|
var
|
||||||
i, j: Integer;
|
i, j: Integer;
|
||||||
begin
|
begin
|
||||||
if (not Assigned(Other)) or (Other.Kind <> stMethod) then
|
var uOther := TTypes.Unwrap(Other);
|
||||||
|
if (not Assigned(uOther)) or (uOther.Kind <> stMethod) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
var otherSigs := Other.Signatures;
|
var otherSigs := uOther.Signatures;
|
||||||
if Length(Self.FSignatures) <> Length(otherSigs) then
|
if Length(Self.FSignatures) <> Length(otherSigs) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
@@ -420,14 +551,18 @@ end;
|
|||||||
function TMethodType.GetHashCode: Integer;
|
function TMethodType.GetHashCode: Integer;
|
||||||
var
|
var
|
||||||
sig: IMethodSignature;
|
sig: IMethodSignature;
|
||||||
|
h: Integer;
|
||||||
begin
|
begin
|
||||||
Result := Ord(stMethod);
|
// Seed with stMethod, then mix in each signature's hash
|
||||||
|
h := Ord(stMethod);
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(TStaticTypeKind), 0);
|
||||||
|
|
||||||
for sig in FSignatures do
|
for sig in FSignatures do
|
||||||
begin
|
begin
|
||||||
if Assigned(sig) then
|
if Assigned(sig) then
|
||||||
begin
|
begin
|
||||||
var hash := sig.GetHashCode;
|
h := sig.GetHashCode;
|
||||||
Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result);
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -471,7 +606,7 @@ type
|
|||||||
constructor Create(AKind: TStaticTypeKind; ADef: IScalarRecordDefinition);
|
constructor Create(AKind: TStaticTypeKind; ADef: IScalarRecordDefinition);
|
||||||
function GetKind: TStaticTypeKind; override;
|
function GetKind: TStaticTypeKind; override;
|
||||||
function GetDefinition: IScalarRecordDefinition; override;
|
function GetDefinition: IScalarRecordDefinition; override;
|
||||||
function IsEqual(const Other: IStaticType): Boolean; override;
|
function IsStructurallyEqual(const Other: IStaticType): Boolean; override;
|
||||||
function GetHashCode: Integer; override;
|
function GetHashCode: Integer; override;
|
||||||
function ToString: string; override;
|
function ToString: string; override;
|
||||||
end;
|
end;
|
||||||
@@ -494,14 +629,15 @@ begin
|
|||||||
Result := FDefinition;
|
Result := FDefinition;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRecordType.IsEqual(const Other: IStaticType): Boolean;
|
function TRecordType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
if (not Assigned(Other)) or (Other.Kind <> GetKind) then
|
var uOther := TTypes.Unwrap(Other);
|
||||||
|
if (not Assigned(uOther)) or (uOther.Kind <> GetKind) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
var otherDef := Other.Definition;
|
var otherDef := uOther.Definition;
|
||||||
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
|
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
@@ -519,19 +655,26 @@ end;
|
|||||||
|
|
||||||
function TRecordType.GetHashCode: Integer;
|
function TRecordType.GetHashCode: Integer;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i, h: Integer;
|
||||||
field: TPair<IKeyword, TScalar.TKind>;
|
field: TPair<IKeyword, TScalar.TKind>;
|
||||||
begin
|
begin
|
||||||
Result := Ord(GetKind);
|
// Seed with Kind
|
||||||
|
Result := THashBobJenkins.GetHashValue(FKind, SizeOf(TStaticTypeKind), 0);
|
||||||
|
|
||||||
if Assigned(FDefinition) then
|
if Assigned(FDefinition) then
|
||||||
begin
|
begin
|
||||||
for i := 0 to FDefinition.Count - 1 do
|
for i := 0 to FDefinition.Count - 1 do
|
||||||
begin
|
begin
|
||||||
field := FDefinition[i];
|
field := FDefinition[i];
|
||||||
var hash := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key);
|
|
||||||
Result := THashBobJenkins.GetHashValue(hash, SizeOf(Pointer), Result);
|
// Hash Key
|
||||||
var data := Ord(field.Value);
|
// Keyword identities are unique by pointer, but let's hash their internal ID/Index for safety
|
||||||
Result := THashBobJenkins.GetHashValue(data, SizeOf(Byte), Result);
|
h := field.Key.Idx;
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
|
|
||||||
|
// Hash Field Type Kind
|
||||||
|
h := Ord(field.Value);
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -564,7 +707,7 @@ type
|
|||||||
constructor Create(const ADef: IGenericRecordDefinition);
|
constructor Create(const ADef: IGenericRecordDefinition);
|
||||||
function GetKind: TStaticTypeKind; override;
|
function GetKind: TStaticTypeKind; override;
|
||||||
function GetGenericDefinition: IGenericRecordDefinition; override;
|
function GetGenericDefinition: IGenericRecordDefinition; override;
|
||||||
function IsEqual(const Other: IStaticType): Boolean; override;
|
function IsStructurallyEqual(const Other: IStaticType): Boolean; override;
|
||||||
function GetHashCode: Integer; override;
|
function GetHashCode: Integer; override;
|
||||||
function ToString: string; override;
|
function ToString: string; override;
|
||||||
end;
|
end;
|
||||||
@@ -585,14 +728,15 @@ begin
|
|||||||
Result := FDefinition;
|
Result := FDefinition;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TGenericRecordType.IsEqual(const Other: IStaticType): Boolean;
|
function TGenericRecordType.IsStructurallyEqual(const Other: IStaticType): Boolean;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
if (not Assigned(Other)) or (Other.Kind <> stGenericRecord) then
|
var uOther := TTypes.Unwrap(Other);
|
||||||
|
if (not Assigned(uOther)) or (uOther.Kind <> stGenericRecord) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
var otherDef := Other.GenericDefinition;
|
var otherDef := uOther.GenericDefinition;
|
||||||
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
|
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
@@ -601,6 +745,7 @@ begin
|
|||||||
|
|
||||||
for i := 0 to Self.FDefinition.Count - 1 do
|
for i := 0 to Self.FDefinition.Count - 1 do
|
||||||
begin
|
begin
|
||||||
|
// Deep Check of Field Types
|
||||||
if (Self.FDefinition[i].Key <> otherDef[i].Key) or (not Self.FDefinition[i].Value.IsEqual(otherDef[i].Value)) then
|
if (Self.FDefinition[i].Key <> otherDef[i].Key) or (not Self.FDefinition[i].Value.IsEqual(otherDef[i].Value)) then
|
||||||
exit(False);
|
exit(False);
|
||||||
end;
|
end;
|
||||||
@@ -610,21 +755,27 @@ end;
|
|||||||
|
|
||||||
function TGenericRecordType.GetHashCode: Integer;
|
function TGenericRecordType.GetHashCode: Integer;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i, h: Integer;
|
||||||
field: TPair<IKeyword, IStaticType>;
|
field: TPair<IKeyword, IStaticType>;
|
||||||
begin
|
begin
|
||||||
Result := Ord(stGenericRecord);
|
h := Ord(stGenericRecord);
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(TStaticTypeKind), 0);
|
||||||
|
|
||||||
if Assigned(FDefinition) then
|
if Assigned(FDefinition) then
|
||||||
begin
|
begin
|
||||||
for i := 0 to FDefinition.Count - 1 do
|
for i := 0 to FDefinition.Count - 1 do
|
||||||
begin
|
begin
|
||||||
field := FDefinition[i];
|
field := FDefinition[i];
|
||||||
var data := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key);
|
|
||||||
Result := THashBobJenkins.GetHashValue(data, SizeOf(Pointer), Result);
|
// Hash Key (using Idx)
|
||||||
|
h := field.Key.Idx;
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
|
|
||||||
|
// Hash Value Type
|
||||||
if Assigned(field.Value) then
|
if Assigned(field.Value) then
|
||||||
begin
|
begin
|
||||||
var hash := field.Value.GetHashCode;
|
h := field.Value.GetHashCode;
|
||||||
Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result);
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -649,10 +800,66 @@ begin
|
|||||||
Result := Result + '}';
|
Result := Result + '}';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TTypes (Factory) }
|
// --- TMethodSigArrayComparer ---
|
||||||
|
|
||||||
|
type
|
||||||
|
TMethodSigArrayComparer = class(TEqualityComparer<TArray<IMethodSignature>>)
|
||||||
|
public
|
||||||
|
function Equals(const Left, Right: TArray<IMethodSignature>): Boolean; override;
|
||||||
|
function GetHashCode(const Value: TArray<IMethodSignature>): Integer; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMethodSigArrayComparer.Equals(const Left, Right: TArray<IMethodSignature>): Boolean;
|
||||||
|
var
|
||||||
|
i, j: Integer;
|
||||||
|
begin
|
||||||
|
if Length(Left) <> Length(Right) then
|
||||||
|
exit(False);
|
||||||
|
for i := 0 to High(Left) do
|
||||||
|
begin
|
||||||
|
if not Left[i].ReturnType.IsEqual(Right[i].ReturnType) then
|
||||||
|
exit(False);
|
||||||
|
|
||||||
|
var pLeft := Left[i].ParamTypes;
|
||||||
|
var pRight := Right[i].ParamTypes;
|
||||||
|
if Length(pLeft) <> Length(pRight) then
|
||||||
|
exit(False);
|
||||||
|
|
||||||
|
for j := 0 to High(pLeft) do
|
||||||
|
if not pLeft[j].IsEqual(pRight[j]) then
|
||||||
|
exit(False);
|
||||||
|
end;
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMethodSigArrayComparer.GetHashCode(const Value: TArray<IMethodSignature>): Integer;
|
||||||
|
var
|
||||||
|
sig: IMethodSignature;
|
||||||
|
p: IStaticType;
|
||||||
|
h: Integer;
|
||||||
|
begin
|
||||||
|
Result := 0;
|
||||||
|
for sig in Value do
|
||||||
|
begin
|
||||||
|
if Assigned(sig.ReturnType) then
|
||||||
|
begin
|
||||||
|
h := sig.ReturnType.GetHashCode;
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
|
end;
|
||||||
|
|
||||||
|
for p in sig.ParamTypes do
|
||||||
|
begin
|
||||||
|
h := p.GetHashCode;
|
||||||
|
Result := THashBobJenkins.GetHashValue(h, SizeOf(Integer), Result);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TTypes Factory }
|
||||||
|
|
||||||
class constructor TTypes.Create;
|
class constructor TTypes.Create;
|
||||||
begin
|
begin
|
||||||
|
// 1. Primitives
|
||||||
FUnknown := TSimpleStaticType.Create(stUnknown);
|
FUnknown := TSimpleStaticType.Create(stUnknown);
|
||||||
FVoid := TSimpleStaticType.Create(stVoid);
|
FVoid := TSimpleStaticType.Create(stVoid);
|
||||||
FOrdinal := TSimpleStaticType.Create(stOrdinal);
|
FOrdinal := TSimpleStaticType.Create(stOrdinal);
|
||||||
@@ -661,42 +868,158 @@ begin
|
|||||||
FDateTime := TSimpleStaticType.Create(stDateTime);
|
FDateTime := TSimpleStaticType.Create(stDateTime);
|
||||||
FText := TSimpleStaticType.Create(stText);
|
FText := TSimpleStaticType.Create(stText);
|
||||||
FKeyword := TSimpleStaticType.Create(stKeyword);
|
FKeyword := TSimpleStaticType.Create(stKeyword);
|
||||||
|
|
||||||
|
// 2. Optional Primitives (pre-cached)
|
||||||
|
FOrdinalOpt := TOptionalType.Create(FOrdinal);
|
||||||
|
FFloatOpt := TOptionalType.Create(FFloat);
|
||||||
|
FBooleanOpt := TOptionalType.Create(FBoolean);
|
||||||
|
FDateTimeOpt := TOptionalType.Create(FDateTime);
|
||||||
|
FTextOpt := TOptionalType.Create(FText);
|
||||||
|
FKeywordOpt := TOptionalType.Create(FKeyword);
|
||||||
|
|
||||||
|
// 3. Caches
|
||||||
|
FSeriesCache := TDictionary<IStaticType, IStaticType>.Create;
|
||||||
|
FRecordCache := TDictionary<IScalarRecordDefinition, IStaticType>.Create;
|
||||||
|
FRecordSeriesCache := TDictionary<IScalarRecordDefinition, IStaticType>.Create;
|
||||||
|
FGenericRecordCache := TDictionary<IGenericRecordDefinition, IStaticType>.Create;
|
||||||
|
FMethodCache := TDictionary<TArray<IMethodSignature>, IStaticType>.Create(TMethodSigArrayComparer.Create);
|
||||||
|
FOptionalCache := TDictionary<IStaticType, IStaticType>.Create;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class destructor TTypes.Destroy;
|
||||||
|
begin
|
||||||
|
FSeriesCache.Free;
|
||||||
|
FRecordCache.Free;
|
||||||
|
FRecordSeriesCache.Free;
|
||||||
|
FGenericRecordCache.Free;
|
||||||
|
FMethodCache.Free;
|
||||||
|
FOptionalCache.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TTypes.CreateSeries(const AElementType: IStaticType): IStaticType;
|
||||||
|
begin
|
||||||
|
if not Assigned(AElementType) then
|
||||||
|
exit(FUnknown);
|
||||||
|
|
||||||
|
TMonitor.Enter(FSeriesCache);
|
||||||
|
try
|
||||||
|
if FSeriesCache.TryGetValue(AElementType, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TSeriesType.Create(AElementType);
|
||||||
|
FSeriesCache.Add(AElementType, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FSeriesCache);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TTypes.CreateRecord(const ADef: IScalarRecordDefinition): IStaticType;
|
||||||
|
begin
|
||||||
|
TMonitor.Enter(FRecordCache);
|
||||||
|
try
|
||||||
|
if FRecordCache.TryGetValue(ADef, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TRecordType.Create(stRecord, ADef);
|
||||||
|
FRecordCache.Add(ADef, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FRecordCache);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TTypes.CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType;
|
||||||
|
begin
|
||||||
|
TMonitor.Enter(FRecordSeriesCache);
|
||||||
|
try
|
||||||
|
if FRecordSeriesCache.TryGetValue(ADef, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TRecordType.Create(stRecordSeries, ADef);
|
||||||
|
FRecordSeriesCache.Add(ADef, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FRecordSeriesCache);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TTypes.CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType;
|
||||||
|
begin
|
||||||
|
TMonitor.Enter(FGenericRecordCache);
|
||||||
|
try
|
||||||
|
if FGenericRecordCache.TryGetValue(ADef, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TGenericRecordType.Create(ADef);
|
||||||
|
FGenericRecordCache.Add(ADef, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FGenericRecordCache);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypes.CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType;
|
class function TTypes.CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType;
|
||||||
var
|
var
|
||||||
sig: IMethodSignature;
|
sig: IMethodSignature;
|
||||||
sigs: TArray<IMethodSignature>;
|
|
||||||
begin
|
begin
|
||||||
sig := TMethodSignature.Create(AParamTypes, AReturnType);
|
sig := TMethodSignature.Create(AParamTypes, AReturnType);
|
||||||
SetLength(sigs, 1);
|
Result := CreateMethodSet([sig]);
|
||||||
sigs[0] := sig;
|
|
||||||
Result := TMethodType.Create(sigs);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypes.CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType;
|
class function TTypes.CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType;
|
||||||
begin
|
begin
|
||||||
Result := TMethodType.Create(ASignatures);
|
if Length(ASignatures) = 0 then
|
||||||
|
exit(FUnknown);
|
||||||
|
|
||||||
|
TMonitor.Enter(FMethodCache);
|
||||||
|
try
|
||||||
|
if FMethodCache.TryGetValue(ASignatures, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TMethodType.Create(ASignatures);
|
||||||
|
FMethodCache.Add(ASignatures, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FMethodCache);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypes.CreateRecord(const ADef: IScalarRecordDefinition): IStaticType;
|
class function TTypes.MakeOptional(const AType: IStaticType): IStaticType;
|
||||||
begin
|
begin
|
||||||
Result := TRecordType.Create(stRecord, ADef);
|
if (not Assigned(AType)) or (AType.Kind = stUnknown) or (AType.Kind = stVoid) then
|
||||||
|
exit(AType);
|
||||||
|
|
||||||
|
if AType.IsOptional then
|
||||||
|
exit(AType);
|
||||||
|
|
||||||
|
// Fast-Path using pointer comparison on flyweights
|
||||||
|
if Pointer(AType) = Pointer(FOrdinal) then
|
||||||
|
exit(FOrdinalOpt);
|
||||||
|
if Pointer(AType) = Pointer(FFloat) then
|
||||||
|
exit(FFloatOpt);
|
||||||
|
if Pointer(AType) = Pointer(FBoolean) then
|
||||||
|
exit(FBooleanOpt);
|
||||||
|
if Pointer(AType) = Pointer(FText) then
|
||||||
|
exit(FTextOpt);
|
||||||
|
if Pointer(AType) = Pointer(FKeyword) then
|
||||||
|
exit(FKeywordOpt);
|
||||||
|
if Pointer(AType) = Pointer(FDateTime) then
|
||||||
|
exit(FDateTimeOpt);
|
||||||
|
|
||||||
|
TMonitor.Enter(FOptionalCache);
|
||||||
|
try
|
||||||
|
if FOptionalCache.TryGetValue(AType, Result) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TOptionalType.Create(AType);
|
||||||
|
FOptionalCache.Add(AType, Result);
|
||||||
|
finally
|
||||||
|
TMonitor.Exit(FOptionalCache);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypes.CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType;
|
class function TTypes.Unwrap(const AType: IStaticType): IStaticType;
|
||||||
begin
|
begin
|
||||||
Result := TRecordType.Create(stRecordSeries, ADef);
|
if (Assigned(AType)) and (AType is TOptionalType) then
|
||||||
end;
|
Result := (AType as TOptionalType).FInner
|
||||||
|
else
|
||||||
class function TTypes.CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType;
|
Result := AType;
|
||||||
begin
|
|
||||||
Result := TGenericRecordType.Create(ADef);
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TTypes.CreateSeries(const AElementType: IStaticType): IStaticType;
|
|
||||||
begin
|
|
||||||
Result := TSeriesType.Create(AElementType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypes.FromScalarKind(AKind: TScalar.TKind): IStaticType;
|
class function TTypes.FromScalarKind(AKind: TScalar.TKind): IStaticType;
|
||||||
@@ -708,7 +1031,6 @@ begin
|
|||||||
TScalar.TKind.Boolean: Result := FBoolean;
|
TScalar.TKind.Boolean: Result := FBoolean;
|
||||||
TScalar.TKind.DateTime: Result := FDateTime;
|
TScalar.TKind.DateTime: Result := FDateTime;
|
||||||
else
|
else
|
||||||
Assert(False, 'Invalid Scalar Kind');
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -723,22 +1045,26 @@ begin
|
|||||||
if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then
|
if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then
|
||||||
exit(True);
|
exit(True);
|
||||||
|
|
||||||
if Target.IsEqual(Source) then
|
// STRICTNESS RULE: Cannot assign Optional to Non-Optional
|
||||||
|
if Source.IsOptional and (not Target.IsOptional) then
|
||||||
|
exit(False);
|
||||||
|
|
||||||
|
var uTarget := TTypes.Unwrap(Target);
|
||||||
|
var uSource := TTypes.Unwrap(Source);
|
||||||
|
|
||||||
|
if uTarget.IsStructurallyEqual(uSource) then
|
||||||
exit(True);
|
exit(True);
|
||||||
|
|
||||||
if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then
|
// Auto-Promotion rules
|
||||||
|
if (uTarget.Kind = stFloat) and (uSource.Kind = stOrdinal) then
|
||||||
exit(True);
|
exit(True);
|
||||||
|
if (uTarget.Kind = stOrdinal) and (uSource.Kind = stBoolean) then
|
||||||
if (Target.Kind = stOrdinal) and (Source.Kind = stBoolean) then
|
|
||||||
exit(True);
|
exit(True);
|
||||||
|
if (uTarget.Kind = stFloat) and (uSource.Kind = stDateTime) then
|
||||||
if (Target.Kind = stFloat) and (Source.Kind = stDateTime) then
|
|
||||||
exit(True);
|
exit(True);
|
||||||
|
if (uTarget.Kind = stVoid) and (uSource.Kind = stMethod) then
|
||||||
if (Target.Kind = stVoid) and (Source.Kind = stMethod) then
|
|
||||||
exit(True);
|
exit(True);
|
||||||
|
if (uTarget.Kind = stOrdinal) and (uSource.Kind = stKeyword) then
|
||||||
if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then
|
|
||||||
exit(True);
|
exit(True);
|
||||||
|
|
||||||
Result := False;
|
Result := False;
|
||||||
@@ -746,49 +1072,57 @@ end;
|
|||||||
|
|
||||||
class function TTypeRules.Promote(const A, B: IStaticType): IStaticType;
|
class function TTypeRules.Promote(const A, B: IStaticType): IStaticType;
|
||||||
begin
|
begin
|
||||||
// Handle Unknown: If one is Unknown, the result is the other type.
|
|
||||||
if A.Kind = stUnknown then
|
if A.Kind = stUnknown then
|
||||||
exit(B);
|
exit(B);
|
||||||
if B.Kind = stUnknown then
|
if B.Kind = stUnknown then
|
||||||
exit(A);
|
exit(A);
|
||||||
|
|
||||||
// all operations involving void result void
|
// T + Void -> T?
|
||||||
if (A.Kind = stVoid) or (B.Kind = stVoid) then
|
if (A.Kind = stVoid) and (B.Kind <> stVoid) then
|
||||||
|
exit(TTypes.MakeOptional(B));
|
||||||
|
if (B.Kind = stVoid) and (A.Kind <> stVoid) then
|
||||||
|
exit(TTypes.MakeOptional(A));
|
||||||
|
|
||||||
|
if (A.Kind = stVoid) and (B.Kind = stVoid) then
|
||||||
exit(TTypes.Void);
|
exit(TTypes.Void);
|
||||||
|
|
||||||
// Standard Numeric promotion rule: Float wins
|
var uA := TTypes.Unwrap(A);
|
||||||
if (A.Kind = stFloat) and (B.Kind = stFloat) then
|
var uB := TTypes.Unwrap(B);
|
||||||
exit(TTypes.Float);
|
var common: IStaticType := nil;
|
||||||
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, return that type.
|
if uA.IsStructurallyEqual(uB) then
|
||||||
if A.IsEqual(B) then
|
common := uA
|
||||||
exit(A);
|
else if (uA.Kind = stFloat) and (uB.Kind = stFloat) then
|
||||||
|
common := TTypes.Float
|
||||||
|
else if (uA.Kind = stFloat) and (uB.Kind = stOrdinal) then
|
||||||
|
common := TTypes.Float
|
||||||
|
else if (uA.Kind = stOrdinal) and (uB.Kind = stFloat) then
|
||||||
|
common := TTypes.Float
|
||||||
|
else if (uA.Kind = stOrdinal) and (uB.Kind = stOrdinal) then
|
||||||
|
common := TTypes.Ordinal;
|
||||||
|
|
||||||
// No promotion possible
|
if common = nil then
|
||||||
Result := nil;
|
exit(nil);
|
||||||
|
|
||||||
|
if A.IsOptional or B.IsOptional then
|
||||||
|
Result := TTypes.MakeOptional(common)
|
||||||
|
else
|
||||||
|
Result := common;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType;
|
class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType;
|
||||||
var
|
|
||||||
promotedType: IStaticType;
|
|
||||||
promotedKind: TStaticTypeKind;
|
|
||||||
begin
|
begin
|
||||||
promotedType := Promote(Left, Right);
|
if Left.IsOptional or Right.IsOptional then
|
||||||
|
|
||||||
// Mismatch during promotion
|
|
||||||
if not Assigned(promotedType) then
|
|
||||||
exit(nil);
|
exit(nil);
|
||||||
|
|
||||||
|
var promotedType := Promote(Left, Right);
|
||||||
|
|
||||||
|
if not Assigned(promotedType) then
|
||||||
|
exit(nil);
|
||||||
if promotedType.Kind = stUnknown then
|
if promotedType.Kind = stUnknown then
|
||||||
exit(TTypes.Unknown);
|
exit(TTypes.Unknown);
|
||||||
|
|
||||||
promotedKind := promotedType.Kind;
|
var promotedKind := promotedType.Kind;
|
||||||
|
|
||||||
case Op of
|
case Op of
|
||||||
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
|
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
|
||||||
@@ -819,17 +1153,16 @@ begin
|
|||||||
Result := TTypes.Boolean;
|
Result := TTypes.Boolean;
|
||||||
end;
|
end;
|
||||||
else
|
else
|
||||||
// Operation not supported at the AST level (might need folding later, but here it implies invalid usage)
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TTypeRules.ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType;
|
class function TTypeRules.ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType;
|
||||||
var
|
|
||||||
rightKind: TStaticTypeKind;
|
|
||||||
begin
|
begin
|
||||||
rightKind := Right.Kind;
|
if Right.IsOptional then
|
||||||
|
exit(nil);
|
||||||
|
|
||||||
|
var rightKind := Right.Kind;
|
||||||
if rightKind = stUnknown then
|
if rightKind = stUnknown then
|
||||||
exit(TTypes.Unknown);
|
exit(TTypes.Unknown);
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -560,7 +560,12 @@ begin
|
|||||||
var paramList := TParameterList.Create(AParameters, listId);
|
var paramList := TParameterList.Create(AParameters, listId);
|
||||||
|
|
||||||
var id := TIdentities.Structural(Loc);
|
var id := TIdentities.Structural(Loc);
|
||||||
Result := TLambdaExpressionNode.Create(paramList, ABody, TTypes.Unknown, nil, nil, nil, False, False, id);
|
|
||||||
|
var body := ABody;
|
||||||
|
if body = nil then
|
||||||
|
body := Block([]);
|
||||||
|
|
||||||
|
Result := TLambdaExpressionNode.Create(paramList, body, TTypes.Unknown, nil, nil, nil, False, False, id);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAst.LambdaExpr(
|
class function TAst.LambdaExpr(
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
unit Test.Myc.Ast.NullPropagation;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
DUnitX.TestFramework,
|
||||||
|
System.SysUtils,
|
||||||
|
System.Classes,
|
||||||
|
Myc.Data.Value,
|
||||||
|
Myc.Data.Scalar,
|
||||||
|
Myc.Ast.Environment,
|
||||||
|
Myc.Ast.Nodes, // For exception types
|
||||||
|
Myc.Ast.Script; // WICHTIG: Für Parsing
|
||||||
|
|
||||||
|
type
|
||||||
|
[TestFixture]
|
||||||
|
TTestNullPropagation = class
|
||||||
|
private
|
||||||
|
FEnv: TAstEnvironment;
|
||||||
|
public
|
||||||
|
[Setup]
|
||||||
|
procedure Setup;
|
||||||
|
[TearDown]
|
||||||
|
procedure TearDown;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('ValidRecord', 'true,10')]
|
||||||
|
[TestCase('VoidRecord', 'false,0')]
|
||||||
|
procedure TestMemberAccessPropagation(const Condition: Boolean; const ExpectedVal: Int64);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestNestedPropagation;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestIndexerPropagation;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestStrictArithmeticFailure;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestStrictComparisonFailure;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TTestNullPropagation }
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.Setup;
|
||||||
|
begin
|
||||||
|
// Create a fresh standard environment for each test
|
||||||
|
FEnv := TAstEnvironment.Construct(nil);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TearDown;
|
||||||
|
begin
|
||||||
|
FEnv := Default(TAstEnvironment);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TestMemberAccessPropagation(const Condition: Boolean; const ExpectedVal: Int64);
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
resultVal: TDataValue;
|
||||||
|
begin
|
||||||
|
// Script:
|
||||||
|
// 1. Define 'r' which is EITHER a Record {:a 10} OR Void depending on condition.
|
||||||
|
// 2. Access field .a -> Should propagate Void if r is Void.
|
||||||
|
|
||||||
|
script := Format('(do ' + ' (def r (if %s {:a 10}))' + ' (.a r)' + ')', [BoolToStr(Condition, True).ToLower]);
|
||||||
|
|
||||||
|
resultVal := FEnv.Run(TAstScript.Parse(script));
|
||||||
|
|
||||||
|
if Condition then
|
||||||
|
begin
|
||||||
|
// Case: Object exists
|
||||||
|
Assert.AreEqual(TDataValueKind.vkScalar, resultVal.Kind, 'Should return a scalar');
|
||||||
|
Assert.AreEqual(ExpectedVal, resultVal.AsScalar.Value.AsInt64, 'Should return the field value');
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Case: Object is Void (Null Propagation)
|
||||||
|
Assert.IsTrue(resultVal.IsVoid, 'Accessing member of Void should yield Void');
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TestNestedPropagation;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
resultVal: TDataValue;
|
||||||
|
begin
|
||||||
|
// Chained Access: (get (.data (if false ...)) 0)
|
||||||
|
script := '(do ' + ' (def root (if false {:data (new-series "ordinal")}))' + ' (get (.data root) 0)' + ')';
|
||||||
|
|
||||||
|
resultVal := FEnv.Run(TAstScript.Parse(script));
|
||||||
|
|
||||||
|
Assert.IsTrue(resultVal.IsVoid, 'Nested access on Void root should propagate Void all the way up');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TestIndexerPropagation;
|
||||||
|
var
|
||||||
|
script: string;
|
||||||
|
resultVal: TDataValue;
|
||||||
|
begin
|
||||||
|
// 1. Define 's' as Optional<Series>
|
||||||
|
// 2. Access via Indexer
|
||||||
|
script := '(do ' + ' (def s (if false (new-series "ordinal")))' + ' (get s 42)' + ')';
|
||||||
|
|
||||||
|
// FIX: Parse String to AST Node
|
||||||
|
resultVal := FEnv.Run(TAstScript.Parse(script));
|
||||||
|
|
||||||
|
Assert.IsTrue(resultVal.IsVoid, 'Indexer access on Void series should yield Void');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TestStrictArithmeticFailure;
|
||||||
|
begin
|
||||||
|
// Requirement: Implicit Lifting is FORBIDDEN for arithmetic.
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
FEnv.Run(
|
||||||
|
TAstScript.Parse(
|
||||||
|
'''
|
||||||
|
(do
|
||||||
|
(def a (if true 10)) ; a is Optional<Ordinal>
|
||||||
|
(+ a 5) ; Error: Operator + not defined for Optional
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
end,
|
||||||
|
ECompilationFailed,
|
||||||
|
'Arithmetic on optional types must trigger a compilation error'
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestNullPropagation.TestStrictComparisonFailure;
|
||||||
|
begin
|
||||||
|
// Requirement: Comparisons with Optional types are FORBIDDEN.
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
FEnv.Run(
|
||||||
|
TAstScript.Parse(
|
||||||
|
'(do '
|
||||||
|
+ ' (def a (if true 10))'
|
||||||
|
+ // a is Optional<Ordinal>
|
||||||
|
' (> a 5)'
|
||||||
|
+ // Error
|
||||||
|
')'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
end,
|
||||||
|
ECompilationFailed,
|
||||||
|
'Comparison on optional types must trigger a compilation error'
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+2
-1
@@ -33,7 +33,8 @@ uses
|
|||||||
Test.Myc.Ast.Compiler.Macros in 'AST\Test.Myc.Ast.Compiler.Macros.pas',
|
Test.Myc.Ast.Compiler.Macros in 'AST\Test.Myc.Ast.Compiler.Macros.pas',
|
||||||
Test.Myc.Ast.RTL.DateTime in 'AST\Test.Myc.Ast.RTL.DateTime.pas',
|
Test.Myc.Ast.RTL.DateTime in 'AST\Test.Myc.Ast.RTL.DateTime.pas',
|
||||||
Test.Myc.Ast.Compiler.Binder in '..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas',
|
Test.Myc.Ast.Compiler.Binder in '..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas',
|
||||||
Test.Myc.Ast.RTL.TypeRegistry in 'AST\Test.Myc.Ast.RTL.TypeRegistry.pas';
|
Test.Myc.Ast.RTL.TypeRegistry in 'AST\Test.Myc.Ast.RTL.TypeRegistry.pas',
|
||||||
|
Test.Myc.Ast.NullPropagation in 'AST\Test.Myc.Ast.NullPropagation.pas';
|
||||||
|
|
||||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||||
{$IFNDEF TESTINSIGHT}
|
{$IFNDEF TESTINSIGHT}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<DCCReference Include="AST\Test.Myc.Ast.RTL.DateTime.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.RTL.DateTime.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas"/>
|
<DCCReference Include="..\Src\AST\Test.Myc.Ast.Compiler.Binder.pas"/>
|
||||||
<DCCReference Include="AST\Test.Myc.Ast.RTL.TypeRegistry.pas"/>
|
<DCCReference Include="AST\Test.Myc.Ast.RTL.TypeRegistry.pas"/>
|
||||||
|
<DCCReference Include="AST\Test.Myc.Ast.NullPropagation.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
Reference in New Issue
Block a user