ASt Types
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -20,7 +20,8 @@ uses
|
||||
Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas',
|
||||
Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
|
||||
Myc.Utils in '..\Src\Myc.Utils.pas',
|
||||
Myc.Ast.Script in '..\Src\AST\Myc.Ast.Script.pas';
|
||||
Myc.Ast.Script in '..\Src\AST\Myc.Ast.Script.pas',
|
||||
Myc.Ast.Types in '..\Src\AST\Myc.Ast.Types.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
<DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Utils.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Script.pas"/>
|
||||
<DCCReference Include="..\Src\AST\Myc.Ast.Types.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
|
||||
---
|
||||
|
||||
# Projektplan: "Single-Map-Argument" (SMA) Architektur
|
||||
|
||||
*Datum: 23. Oktober 2025*
|
||||
|
||||
## 1. Motivation: Die "Visuelle Sprache"
|
||||
|
||||
Das primäre Entwicklungsziel ist **nicht** eine Text-basierte Programmiersprache, sondern ein **visueller Editor**, in dem Logik durch das Kombinieren von Blöcken erstellt wird. Der Text-Parser (`Myc.Ast.Script`) ist lediglich eine sekundäre Repräsentation.
|
||||
|
||||
Für einen visuellen Editor ist syntaktische Komplexität Gift. Jede Ausnahme, jedes Schlüsselwort und jede alternative Syntax (wie `[]` vs. `()`) erfordert einen neuen, speziellen visuellen Block, was die Benutzeroberfläche überlädt und die Konsistenz bricht.
|
||||
|
||||
Das ultimative Ziel ist eine **100% einheitliche Syntax**, bei der es nur noch *eine* Art gibt, eine Operation auszudrücken: den Funktionsaufruf. In einer S-Expression-Welt ist die konsistenteste Form dafür `(Funktionsname Argumente...)`.
|
||||
|
||||
## 2. Ziel: Das "Single-Map-Argument" (SMA) Modell
|
||||
|
||||
Um die visuelle Darstellung auf das absolute Minimum zu reduzieren (ein Block für "Funktion" und ein Slot für "Argumente"), führen wir das **"Single-Map-Argument" (SMA) Modell** ein.
|
||||
|
||||
Es gibt nur noch *eine* gültige Aufrufkonvention:
|
||||
`(Funktionsname {Argument-Map})`
|
||||
|
||||
Jede Funktion, egal ob nativ (`+`) oder benutzerdefiniert (`my-lambda`), wird mit einem einzigen Argument aufgerufen: einem **Map-Literal** (das zu einem `TScalarRecord` ausgewertet wird). Dieses Literal definiert die Argumente über Key-Value-Paare.
|
||||
|
||||
**Beispiele:**
|
||||
|
||||
* **Arithmetik:** `(+ {:x 1 :y 2})`
|
||||
* **RTL-Funktion:** `(Abs {:value -10})`
|
||||
* **Lambda-Definition:** `(fn my-adder ({:x1 1 :x2 2}) ...)`
|
||||
* **Lambda-Aufruf:** `(my-adder {:x1 5})`
|
||||
|
||||
Dieses Design erfüllt die Anforderung an die visuelle Konsistenz perfekt. Ein "Aufruf"-Block im Editor hat immer nur zwei definierte Slots: den Namen (z.B. `+`) und ein Map-Literal (z.B. `{:x ..., :y ...}`).
|
||||
|
||||
## 3. Analyse & Performance-Strategie
|
||||
|
||||
Unsere Diskussion hat ergeben, dass dieses Modell zwar visuell perfekt, aber in einer naiven Implementierung performancetechnisch inakzeptabel wäre.
|
||||
|
||||
### Das Problem: Die naive Implementierung (Verworfen)
|
||||
|
||||
Eine naive Implementierung würde jeden Aufruf zur Laufzeit gleich behandeln:
|
||||
1. Der `TEvaluator` wertet das Map-Literal `{:x 1 :y 2}` zu einem vollwertigen `TScalarRecord` aus (Heap-Allokation, Füllen einer Map/Dictionary-Struktur).
|
||||
2. Er ruft die native `+` Funktion mit diesem *einen* `TScalarRecord`-Argument auf.
|
||||
3. Die `+` Funktion (bzw. ihr Wrapper) müsste die Map parsen, die Keys `:x` und `:y` nachschlagen und die Werte extrahieren.
|
||||
|
||||
Für Operationen wie `+`, die millionenfach pro Sekunde aufgerufen werden, ist dieser Overhead (Heap-Allokation + Hashmap-Lookups) katastrophal und ein absoluter Showstopper.
|
||||
|
||||
### Die Lösung: Kompilierung von Aufrufsignaturen
|
||||
|
||||
Wir vermeiden diesen Overhead, indem wir den **`TAstBinder` (den "Compiler")** die "Dekonstruktion" der Argument-Maps zur Compile-Zeit durchführen lassen.
|
||||
|
||||
Wir implementieren eine **Drei-Pfade-Kompilierung** für `VisitFunctionCall`:
|
||||
|
||||
#### Pfad 1: "Fast Path" (Nativ / RTL)
|
||||
|
||||
Dieser Pfad optimiert alle Aufrufe an bekannte, fest verdrahtete RTL-Funktionen.
|
||||
|
||||
* **Aktion (Binder):**
|
||||
1. Der `TAstBinder` erhält ein **statisches Registry nativer Signaturen** (z.B. `TDictionary<string, TArray<string>>`). Dieses mappt Namen auf *geordnete* Key-Listen: `'+' -> [':x', ':y']`, `'Abs' -> [':value']`.
|
||||
2. Bei `(sub {:a 5 :b 3})` schlägt er `sub` nach. Treffer! Signatur ist `[':a', ':b']`.
|
||||
3. Der Binder validiert das `IMapLiteralNode` (alle Keys da? unbekannte Keys?).
|
||||
4. **Argument-Umschreibung (Rewriting):** Der Binder *ignoriert* die Map-Struktur und erzeugt einen neuen, *positionalen* `TArray<IAstNode>`: `[ (Node 5), (Node 3) ]`.
|
||||
5. Er erzeugt einen `TBoundFunctionCallNode`, der auf die native `sub`-Funktion zeigt, aber das *neue positionale Array* als Argumentenliste enthält.
|
||||
* **Aktion (Evaluator):**
|
||||
1. Der Evaluator sieht einen normalen, positionalen Aufruf.
|
||||
2. Er wertet die Argumente `5` und `3` aus und ruft die `TRtlFunctions.Subtract` direkt mit einem `TArray<TDataValue>` auf.
|
||||
* **Ergebnis:** Keinerlei Map-Allokation zur Laufzeit. Maximale Performance.
|
||||
|
||||
#### Pfad 2: "Fast Path" (Direkte Lambda)
|
||||
|
||||
Dieser Pfad optimiert direkte Aufrufe an Lambdas, deren Definition dem Binder bereits bekannt ist.
|
||||
|
||||
* **Aktion (Binder):**
|
||||
1. Bei `(fn my-adder ({:x1 1 :x2 2}) ...)` parst der Binder die Signatur `({:x1 1, :x2 2})`.
|
||||
2. Er speichert diese Signatur-Metadaten (Keys und Default-Wert-Nodes) im `IScopeDescriptor` als Metadatum für die Variable `my-adder`.
|
||||
3. Bei einem späteren Aufruf `(my-adder {:x1 5})`:
|
||||
4. Der Binder schlägt `my-adder` im Scope nach. Treffer! Er findet die Variable *und* die gespeicherte Signatur.
|
||||
5. **Argument-Umschreibung:** Er führt die *gleiche* Optimierung wie bei nativen Aufrufen durch. Er parst das `IMapLiteralNode`, füllt fehlende Keys mit den Default-Nodes (z.B. `:x2` -> `(Node 2)`) und erzeugt einen positionalen `TArray<IAstNode>`: `[ (Node 5), (Node 2) ]`.
|
||||
6. Er erzeugt einen `TBoundFunctionCallNode`, der das positionale Array enthält.
|
||||
* **Aktion (Evaluator):**
|
||||
1. Der Evaluator wertet die Closure `my-adder` aus.
|
||||
2. Er wertet die Argumente `5` und `2` aus.
|
||||
3. Er ruft die Closure mit einem *positionalen* `TArray<TDataValue>` auf.
|
||||
* **Ergebnis:** Auch hier: Keinerlei Map-Allokation zur Laufzeit.
|
||||
|
||||
#### Pfad 3: "Slow Path" (Dynamisch / Polymorph)
|
||||
|
||||
Dies ist der Fallback für alle Aufrufe, die der Binder zur Compile-Zeit *unmöglich* auflösen kann.
|
||||
|
||||
* **Szenarien:**
|
||||
* **Funktionen höherer Ordnung (HOFs):** `(Map my-series (fn ({:item}) ...))` -> Die `Map`-Funktion *muss* die Lambda dynamisch aufrufen.
|
||||
* **Indirekte Aufrufe:** `(fn call-it (func) (func {:a 1}))` -> `func` ist zur Compile-Zeit unbekannt.
|
||||
* **Späte Bindung / Forward-Deklarationen.**
|
||||
* **Aktion (Binder):**
|
||||
1. Der Binder kann die Signatur nicht finden.
|
||||
2. Er kann *nicht* optimieren. Er behandelt das `IMapLiteralNode` `{:a 1}` als regulären Wert.
|
||||
3. Er erzeugt einen `TBoundFunctionCallNode` mit einem Argumenten-Array, das *nur dieses eine* `IMapLiteralNode` enthält.
|
||||
* **Aktion (Evaluator):**
|
||||
1. Der Evaluator *muss* nun das `IMapLiteralNode` zu einem echten `TScalarRecord` auswerten (der "Overhead" entsteht hier).
|
||||
2. Er ruft die Closure (z.B. `func`) mit *einem* Argument auf (dem `TScalarRecord`).
|
||||
3. Die Closure selbst muss die **Laufzeit-Destrukturierung** durchführen: Sie parst die Map, extrahiert die Keys (`:a`) und wendet ihre Defaults an.
|
||||
* **Ergebnis:** Das System bleibt voll funktionsfähig und konsistent, nutzt aber den langsameren Pfad nur, wenn es semantisch unvermeidbar ist.
|
||||
|
||||
---
|
||||
|
||||
## 4. TODOs (Detailliert)
|
||||
|
||||
1. **Parser (`Myc.Ast.Script`)**
|
||||
* [ ] `TLexer`: `tkLeftBracket`, `tkRightBracket` entfernen. `tkLBrace` (`{`), `tkRBrace` (`}`), `tkColon` (`:`) hinzufügen.
|
||||
* [ ] `IAstNode`: `IKeywordNode` (für `:key`) und `IMapLiteralNode` (enthält `TArray<TPair<IKeywordNode, IAstNode>>`) definieren.
|
||||
* [ ] `TParser`: `ParseExpression` erweitern, um `{...}` zu `IMapLiteralNode` und `:...` zu `IKeywordNode` zu parsen.
|
||||
* [ ] `TParser`: `ParseList` anpassen. Die `fn`- und `defmacro`-Logik muss die Parameterliste jetzt als `IMapLiteralNode` (für die Destrukturierung) statt einer Liste von Identifiern parsen.
|
||||
|
||||
2. **Binder (`Myc.Ast.Binding`)**
|
||||
* [ ] `TAstBinder.Create`: Das **Native Signature Registry** (`TDictionary<string, TArray<string>>`) initialisieren.
|
||||
* [ ] `TAstBinder.VisitLambdaExpression`: Die `IMapLiteralNode`-Signatur parsen. Die Metadaten (geordnete Keys und Default-`IAstNode`s) müssen in der `TBoundLambdaExpressionNode` oder im `IScopeDescriptor` gespeichert werden.
|
||||
* [ ] `TAstBinder.VisitFunctionCall`: Die **Kern-Drei-Pfade-Logik** implementieren:
|
||||
1. Lookup im Native Registry (Pfad 1).
|
||||
2. Lookup im `IScopeDescriptor` nach Lambda-Metadaten (Pfad 2).
|
||||
3. Fallback auf "Slow Path" (Pfad 3).
|
||||
* [ ] `TAstBinder`: Eine private `function RewriteArguments(const Signature: ...; const CallMap: IMapLiteralNode): TArray<IAstNode>` implementieren, die die "Fast Path"-Dekonstruktion durchführt.
|
||||
|
||||
3. **Evaluator (`Myc.Ast.Evaluator`)**
|
||||
* [ ] `TEvaluatorVisitor`: `VisitMapLiteral` implementieren. Diese Methode wertet ein `IMapLiteralNode` zu einem `TDataValue (vkRecord)` aus (der "Slow Path"-Overhead).
|
||||
* [ ] `TEvaluatorVisitor.VisitLambdaExpression`: Die *Laufzeit*-Destrukturierungslogik implementieren. Diese wird aktiv, wenn die Closure (TFunc) mit einem `TDataValue (vkRecord)` (Pfad 3) anstelle eines `TArray<TDataValue>` (Pfad 2) aufgerufen wird. (Benötigt Anpassung der Closure-Signatur oder -Logik).
|
||||
|
||||
4. **RTL (Anpassung & Registrierung)**
|
||||
* [ ] `Myc.Ast.RTL.Core`: Die Implementierungen (`TRtlFunctions.Add` etc.) bleiben **unverändert**. Sie erwarten weiterhin `TArray<TDataValue>`, da der Binder für sie übersetzt.
|
||||
* [ ] `Myc.Ast.RTL`: Die RTTI-Registrierung muss erweitert werden, um dem Binder die Signaturen bereitzustellen. `TRtlFunctionAttribute` könnte erweitert werden: `[TRtlFunction('+', ':x,:y')]`. Diese Infos füllen das Native Registry im Binder.
|
||||
@@ -0,0 +1,374 @@
|
||||
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.
|
||||
Reference in New Issue
Block a user