Achieve self-hosting for hello-world compilation
The blaise compiler now compiles its own source (tests/blaise-compiler.pas) into a second-stage binary that successfully compiles and runs a hello-world program. All 834 compiler tests still pass. Codegen and RTL fixes that unblocked self-hosting: - String literal interning (uCodeGenQBE): set FStrLits.CaseSensitive := True. FPC's TStringList.IndexOf is case-insensitive by default, so distinct literals like 'WRITELN' and 'WriteLn' collapsed to the same label. - ARC on implicit Self.Field assignments (uCodeGenQBE): EmitAssignment's implicit-Self branch did a raw storel for class/string fields. Fresh _ClassAlloc'ed objects start at refcnt 0, so without the addref they were freed by later local releases. Now addrefs new value and releases old. - #nn character literals (uLexer): UnescapeString handled only 'text'. Extended to decode #nn decimal literals and concatenated forms like 'abc'#13#10'def'. Local OrdAt helper lets the body parse under FPC and the self-hosted compiler. - Chr() builtin: registered in uSymbolTable, handled in uSemantic, emitted in uCodeGenQBE as _Chr, implemented in rtl/blaise_str.c. - LF line separator (Classes.pas): TStringList.GetText used #13#10; QBE rejects CR in its input. Changed to #10. Self-hosting source (tests/blaise-compiler.pas) — needs a virtual Destroy on TASTNode and Exception (post-TObject-stripping they became root classes without a vtable, breaking `is` checks and exception message layout). TAST* descendants chain `override`. Migration tooling: add tools/migrate_full.py — the script that generates tests/blaise-compiler.pas by flattening the eight compiler units and RTL Classes unit into a single self-contained Pascal source. The postprocess() step injects virtual Destroy on TASTNode / TTypeDesc and strips `override` from non-vtable root classes.
This commit is contained in:
parent
49a1a0f478
commit
f66260093e
|
|
@ -49,10 +49,15 @@ type
|
|||
Name: string;
|
||||
IsVarParam: Boolean; { set by uSemantic — True if this ident is a var parameter }
|
||||
IsConstant: Boolean; { set by uSemantic — True if this ident is a skConstant symbol }
|
||||
ConstValue: Int64; { valid when IsConstant = True }
|
||||
ConstValue: Int64; { valid when IsConstant = True; integer/bool/enum value }
|
||||
ConstString: string; { valid when IsConstant = True and type is tyString }
|
||||
IsNoArgFuncCall: Boolean; { set by uSemantic — bare ident that resolves to a 0-arg function }
|
||||
NoArgFuncDecl: TObject; { TMethodDecl — not owned; valid when IsNoArgFuncCall and user-defined }
|
||||
IsGlobal: Boolean; { set by uSemantic — this ident is a program-level global var }
|
||||
IsImplicitSelf: Boolean; { set by uSemantic — bare field name implicitly referencing Self }
|
||||
ImplicitFieldInfo: TObject; { TFieldInfo — not owned; valid when IsImplicitSelf }
|
||||
IsImplicitSelfMethod: Boolean; { set by uSemantic — bare zero-arg method call on Self }
|
||||
ImplicitMethodDecl: TObject; { TMethodDecl — not owned; set when IsImplicitSelfMethod }
|
||||
end;
|
||||
|
||||
TFieldAccessExpr = class(TASTExpr)
|
||||
|
|
@ -65,6 +70,11 @@ type
|
|||
IsClassAccess: Boolean; { set by uSemantic — pointer deref needed }
|
||||
PropRead: TPropertyInfo; { non-nil if this is a method-backed property read }
|
||||
PropOwnerType: string; { class type name for method-backed property calls }
|
||||
IsImplicitSelf: Boolean; { set by uSemantic — RecordName is a field of Self }
|
||||
ImplicitBaseInfo: TFieldInfo; { non-owned — the field of Self holding the record/class }
|
||||
IsMethodCall: Boolean; { set by uSemantic — FieldName is a zero-arg method }
|
||||
ResolvedMethod: TObject; { TMethodDecl — not owned; set when IsMethodCall }
|
||||
IsGlobal: Boolean; { set by uSemantic — RecordName is a program-level global }
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
|
|
@ -111,6 +121,7 @@ type
|
|||
Name: string;
|
||||
Expr: TASTExpr; { owned }
|
||||
IsVarParam: Boolean; { set by uSemantic — True if target is a var parameter }
|
||||
IsGlobal: Boolean; { set by uSemantic — True if target is a program-level global }
|
||||
ResolvedLhsType: TTypeDesc; { set by uSemantic — type of the target variable }
|
||||
IsWeakLhs: Boolean; { set by uSemantic — True if the LHS symbol
|
||||
was declared [Weak]; codegen emits a
|
||||
|
|
@ -145,6 +156,7 @@ type
|
|||
TForStmt = class(TASTStmt)
|
||||
public
|
||||
VarName: string;
|
||||
IsGlobal: Boolean; { set by uSemantic — VarName is a program-level global }
|
||||
StartExpr: TASTExpr; { owned }
|
||||
EndExpr: TASTExpr; { owned }
|
||||
IsDownTo: Boolean;
|
||||
|
|
@ -178,6 +190,9 @@ type
|
|||
{ 'break' statement — exits the innermost loop. }
|
||||
TBreakStmt = class(TASTStmt);
|
||||
|
||||
{ 'continue' statement — jumps to next iteration of the innermost loop. }
|
||||
TContinueStmt = class(TASTStmt);
|
||||
|
||||
{ One arm of a case statement: one or more ordinal values → body }
|
||||
TCaseBranch = class
|
||||
public
|
||||
|
|
@ -201,8 +216,12 @@ type
|
|||
RecordName: string;
|
||||
FieldName: string;
|
||||
Expr: TASTExpr; { owned }
|
||||
ObjExpr: TASTExpr; { owned — when non-nil, receiver is this expression (e.g. typecast) }
|
||||
FieldInfo: TFieldInfo; { set by uSemantic — carries offset + type }
|
||||
IsClassAccess: Boolean; { set by uSemantic — pointer deref needed }
|
||||
IsImplicitSelf: Boolean; { set by uSemantic — RecordName is a field of Self }
|
||||
ImplicitBaseInfo: TFieldInfo; { non-owned — the field of Self that holds the record/class }
|
||||
IsGlobal: Boolean; { set by uSemantic — RecordName is a program-level global }
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
|
|
@ -220,6 +239,7 @@ type
|
|||
Name: string;
|
||||
Args: TObjectList; { owned TASTExpr items }
|
||||
ResolvedDecl: TObject; { TMethodDecl — not owned; set by uSemantic for user-defined procs }
|
||||
IsImplicitSelfMethod: Boolean; { set by uSemantic — call is on Self }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -229,6 +249,7 @@ type
|
|||
Name: string;
|
||||
Args: TObjectList; { owned TASTExpr items }
|
||||
ResolvedDecl: TObject; { TMethodDecl — not owned; set by uSemantic }
|
||||
IsImplicitSelfMethod: Boolean; { set by uSemantic — call is on Self }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -245,9 +266,13 @@ type
|
|||
ObjectName: string;
|
||||
Name: string; { method name }
|
||||
Args: TObjectList; { owned TASTExpr items }
|
||||
ObjExpr: TASTExpr; { owned — when non-nil, receiver is this expression }
|
||||
{ Set by uSemantic: }
|
||||
ResolvedClassType: TTypeDesc; { not owned }
|
||||
ResolvedMethod: TObject; { TMethodDecl — not owned; avoids forward ref }
|
||||
IsImplicitSelf: Boolean; { ObjectName is a field of Self }
|
||||
ImplicitBaseInfo: TFieldInfo; { not owned — the field of Self }
|
||||
IsGlobal: Boolean; { set by uSemantic — ObjectName is a program-level global }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -283,6 +308,7 @@ type
|
|||
IsWeak: Boolean; { set by uSemantic when [Weak] is resolved
|
||||
on this declaration. Codegen keys off
|
||||
this instead of walking Attributes. }
|
||||
IsGlobal: Boolean; { set by uSemantic when this is a program-level global variable }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -355,9 +381,11 @@ type
|
|||
ObjectName: string;
|
||||
Name: string; { method name }
|
||||
Args: TObjectList; { owned TASTExpr }
|
||||
ObjExpr: TASTExpr; { owned — receiver expression when ObjectName = '' }
|
||||
ResolvedClassType: TTypeDesc; { not owned; set by uSemantic }
|
||||
ResolvedMethod: TObject; { TMethodDecl — not owned }
|
||||
IsConstructorCall: Boolean; { set by uSemantic — TypeName.Create(args) }
|
||||
IsGlobal: Boolean; { set by uSemantic — ObjectName is a program-level global }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -648,6 +676,7 @@ end;
|
|||
destructor TFieldAssignment.Destroy;
|
||||
begin
|
||||
Expr.Free;
|
||||
ObjExpr.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
|
|
@ -707,6 +736,7 @@ end;
|
|||
destructor TMethodCallStmt.Destroy;
|
||||
begin
|
||||
Args.Free;
|
||||
ObjExpr.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
|
|
@ -803,6 +833,7 @@ end;
|
|||
destructor TMethodCallExpr.Destroy;
|
||||
begin
|
||||
Args.Free;
|
||||
ObjExpr.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,6 +60,7 @@ type
|
|||
tkNot,
|
||||
tkExit,
|
||||
tkBreak,
|
||||
tkContinue,
|
||||
tkInherited,
|
||||
tkCase,
|
||||
tkOf,
|
||||
|
|
@ -112,6 +113,14 @@ type
|
|||
|
||||
implementation
|
||||
|
||||
{ Local helper matching the Blaise builtin — lets UnescapeString share one
|
||||
body under FPC and the self-hosted compiler. The migration tool strips
|
||||
this declaration; self-hosted builds resolve OrdAt to the builtin. }
|
||||
function OrdAt(const S: string; I: Integer): Integer;
|
||||
begin
|
||||
Result := Ord(S[I]);
|
||||
end;
|
||||
|
||||
constructor TLexer.Create(const ASource: string);
|
||||
begin
|
||||
inherited Create;
|
||||
|
|
@ -163,6 +172,7 @@ begin
|
|||
else if AUpper = 'NOT' then Result := tkNot
|
||||
else if AUpper = 'EXIT' then Result := tkExit
|
||||
else if AUpper = 'BREAK' then Result := tkBreak
|
||||
else if AUpper = 'CONTINUE' then Result := tkContinue
|
||||
else if AUpper = 'CASE' then Result := tkCase
|
||||
else if AUpper = 'OF' then Result := tkOf
|
||||
else if AUpper = 'CONST' then Result := tkConst
|
||||
|
|
@ -172,36 +182,60 @@ begin
|
|||
end;
|
||||
|
||||
function TLexer.UnescapeString(const ARaw: string): string;
|
||||
{ ARaw is the full source span including surrounding quotes.
|
||||
Handles: 'text' with '' → ' escaping.
|
||||
Phase 1 supports only single-quoted string literals; #nn and ^X forms
|
||||
are not yet part of the Clean Pascal spec. }
|
||||
{ ARaw is the full source span. Handles: 'text' with '' → ' escaping,
|
||||
#nn numeric char literals (decimal), and concatenated runs like
|
||||
'abc'#13#10'def'. Uses OrdAt so the body parses under both FPC and
|
||||
the self-hosted Blaise compiler. }
|
||||
var
|
||||
I, Len: Integer;
|
||||
I, Len, N, C: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
Len := Length(ARaw);
|
||||
if (Len >= 2) and (ARaw[1] = '''') then
|
||||
I := 1;
|
||||
while I <= Len do
|
||||
begin
|
||||
I := 2;
|
||||
while I <= Len do
|
||||
C := OrdAt(ARaw, I);
|
||||
if C = 39 then { single quote }
|
||||
begin
|
||||
if ARaw[I] = '''' then
|
||||
I := I + 1;
|
||||
while I <= Len do
|
||||
begin
|
||||
if (I < Len) and (ARaw[I + 1] = '''') then
|
||||
C := OrdAt(ARaw, I);
|
||||
if C = 39 then
|
||||
begin
|
||||
Result := Result + '''';
|
||||
Inc(I, 2);
|
||||
if (I < Len) and (OrdAt(ARaw, I + 1) = 39) then
|
||||
begin
|
||||
Result := Result + '''';
|
||||
I := I + 2;
|
||||
end
|
||||
else
|
||||
begin
|
||||
I := I + 1;
|
||||
Break;
|
||||
end;
|
||||
end
|
||||
else
|
||||
Inc(I); { closing quote }
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := Result + ARaw[I];
|
||||
Inc(I);
|
||||
begin
|
||||
Result := Result + Chr(C);
|
||||
I := I + 1;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else if C = 35 then { '#' }
|
||||
begin
|
||||
I := I + 1;
|
||||
N := 0;
|
||||
while I <= Len do
|
||||
begin
|
||||
C := OrdAt(ARaw, I);
|
||||
if (C < 48) or (C > 57) then Break;
|
||||
N := N * 10 + (C - 48);
|
||||
I := I + 1;
|
||||
end;
|
||||
Result := Result + Chr(N);
|
||||
end
|
||||
else
|
||||
I := I + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -239,6 +273,7 @@ begin
|
|||
else if text = 'OVERRIDE' then Result.Kind := tkOverride
|
||||
else if text = 'EXIT' then Result.Kind := tkExit
|
||||
else if text = 'BREAK' then Result.Kind := tkBreak
|
||||
else if text = 'CONTINUE' then Result.Kind := tkContinue
|
||||
else if text = 'CASE' then Result.Kind := tkCase
|
||||
else if text = 'OF' then Result.Kind := tkOf
|
||||
else if text = 'CONST' then Result.Kind := tkConst
|
||||
|
|
|
|||
|
|
@ -1015,9 +1015,13 @@ var
|
|||
Assign: TAssignment;
|
||||
FldAssign: TFieldAssignment;
|
||||
MCall: TMethodCallStmt;
|
||||
MCallExpr: TMethodCallExpr;
|
||||
PtrWrite: TPointerWriteStmt;
|
||||
PtrIdNode: TIdentExpr;
|
||||
SecondIdent: string;
|
||||
FldNode: TFieldAccessExpr;
|
||||
CastRcv: TASTExpr;
|
||||
FCallNode: TFuncCallExpr;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
|
|
@ -1072,6 +1076,15 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
if Check(tkContinue) then
|
||||
begin
|
||||
Result := TContinueStmt.Create;
|
||||
Result.Line := FCurrent.Line;
|
||||
Result.Col := FCurrent.Col;
|
||||
Advance;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if Check(tkInherited) then
|
||||
begin
|
||||
Result := ParseInheritedStmt;
|
||||
|
|
@ -1139,20 +1152,138 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
{ Method call: Ident '.' Ident ['(' [args] ')'] }
|
||||
MCall := TMethodCallStmt.Create;
|
||||
MCall.Line := Line;
|
||||
MCall.Col := Col;
|
||||
MCall.ObjectName := Name;
|
||||
MCall.Name := SecondIdent;
|
||||
if Check(tkLParen) then
|
||||
{ Method call: Ident '.' Ident ['(' [args] ')'] — may chain to
|
||||
Ident.Ident.Ident... with further '.' before the final method call. }
|
||||
if Check(tkDot) then
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkRParen) then
|
||||
ParseMethodCallArgList(MCall);
|
||||
Expect(tkRParen);
|
||||
{ Chained: AProg.UsedUnits.Add(...) — build a field-access chain as
|
||||
the receiver expression. }
|
||||
MCall := TMethodCallStmt.Create;
|
||||
MCall.Line := Line;
|
||||
MCall.Col := Col;
|
||||
MCall.ObjectName := '';
|
||||
{ Build chain: AProg -> AProg.UsedUnits }
|
||||
PtrIdNode := TIdentExpr.Create;
|
||||
PtrIdNode.Line := Line;
|
||||
PtrIdNode.Col := Col;
|
||||
PtrIdNode.Name := Name;
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := Line;
|
||||
FldNode.Col := Col;
|
||||
FldNode.Base := PtrIdNode;
|
||||
FldNode.FieldName:= SecondIdent;
|
||||
MCall.ObjExpr := FldNode;
|
||||
while Check(tkDot) do
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkIdent) then
|
||||
raise EParseError.CreateFmt('Expected field or method name at line %d col %d',
|
||||
[FCurrent.Line, FCurrent.Col]);
|
||||
SecondIdent := FCurrent.Value;
|
||||
Advance;
|
||||
if Check(tkDot) then
|
||||
begin
|
||||
{ Still more chain — extend field access path }
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := Line;
|
||||
FldNode.Col := Col;
|
||||
FldNode.Base := MCall.ObjExpr;
|
||||
FldNode.FieldName:= SecondIdent;
|
||||
MCall.ObjExpr := FldNode;
|
||||
end
|
||||
else
|
||||
begin
|
||||
MCall.Name := SecondIdent;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkRParen) then
|
||||
ParseMethodCallArgList(MCall);
|
||||
Expect(tkRParen);
|
||||
end;
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
Result := MCall;
|
||||
end
|
||||
else
|
||||
begin
|
||||
MCall := TMethodCallStmt.Create;
|
||||
MCall.Line := Line;
|
||||
MCall.Col := Col;
|
||||
MCall.ObjectName := Name;
|
||||
MCall.Name := SecondIdent;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkRParen) then
|
||||
ParseMethodCallArgList(MCall);
|
||||
Expect(tkRParen);
|
||||
end;
|
||||
{ Post-method chain: Name.Method(args).Field := value or .Method2(args) }
|
||||
if Check(tkDot) then
|
||||
begin
|
||||
MCallExpr := TMethodCallExpr.Create;
|
||||
MCallExpr.Line := Line;
|
||||
MCallExpr.Col := Col;
|
||||
MCallExpr.ObjectName := MCall.ObjectName;
|
||||
MCallExpr.Name := MCall.Name;
|
||||
while MCall.Args.Count > 0 do
|
||||
MCallExpr.Args.Add(MCall.Args.Extract(MCall.Args.Items[0]));
|
||||
MCall.Free;
|
||||
MCall := nil;
|
||||
CastRcv := MCallExpr;
|
||||
while Check(tkDot) do
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkIdent) then
|
||||
raise EParseError.CreateFmt(
|
||||
'Expected identifier after ''.'' at line %d col %d',
|
||||
[FCurrent.Line, FCurrent.Col]);
|
||||
SecondIdent := FCurrent.Value;
|
||||
Advance;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
MCall := TMethodCallStmt.Create;
|
||||
MCall.Line := Line;
|
||||
MCall.Col := Col;
|
||||
MCall.ObjectName := '';
|
||||
MCall.Name := SecondIdent;
|
||||
MCall.ObjExpr := CastRcv;
|
||||
Advance;
|
||||
if not Check(tkRParen) then
|
||||
ParseMethodCallArgList(MCall);
|
||||
Expect(tkRParen);
|
||||
Result := MCall;
|
||||
Exit;
|
||||
end;
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := Line;
|
||||
FldNode.Col := Col;
|
||||
FldNode.Base := CastRcv;
|
||||
FldNode.FieldName := SecondIdent;
|
||||
CastRcv := FldNode;
|
||||
end;
|
||||
if Check(tkAssign) and (CastRcv is TFieldAccessExpr) then
|
||||
begin
|
||||
Advance;
|
||||
FldAssign := TFieldAssignment.Create;
|
||||
FldAssign.Line := Line;
|
||||
FldAssign.Col := Col;
|
||||
FldAssign.FieldName := TFieldAccessExpr(CastRcv).FieldName;
|
||||
FldAssign.ObjExpr := TFieldAccessExpr(CastRcv).Base;
|
||||
TFieldAccessExpr(CastRcv).Base := nil;
|
||||
CastRcv.Free;
|
||||
FldAssign.Expr := ParseExpr;
|
||||
Result := FldAssign;
|
||||
Exit;
|
||||
end;
|
||||
raise EParseError.CreateFmt(
|
||||
'Expected method call or assignment after chain at line %d col %d',
|
||||
[FCurrent.Line, FCurrent.Col]);
|
||||
end;
|
||||
Result := MCall;
|
||||
end;
|
||||
Result := MCall;
|
||||
end;
|
||||
end
|
||||
else if Check(tkAssign) then
|
||||
|
|
@ -1167,17 +1298,97 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
{ Typecast-then-method-call statement: TCast(expr).Method(args)
|
||||
or chained Ident(arg).Field.Method(args). Detect by peeking for
|
||||
'(' followed by a '.' after the paren closure — parse directly
|
||||
into a TFuncCallExpr receiver so the inner arg lives on. }
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
FCallNode := TFuncCallExpr.Create;
|
||||
FCallNode.Line := Line;
|
||||
FCallNode.Col := Col;
|
||||
FCallNode.Name := Name;
|
||||
Advance; { '(' }
|
||||
if not Check(tkRParen) then
|
||||
begin
|
||||
FCallNode.Args.Add(ParseExpr);
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance;
|
||||
FCallNode.Args.Add(ParseExpr);
|
||||
end;
|
||||
end;
|
||||
Expect(tkRParen);
|
||||
if Check(tkDot) then
|
||||
begin
|
||||
CastRcv := FCallNode;
|
||||
{ Chain parse: each step reads '.Ident', then either '(' (call) or
|
||||
more '.'. The final method call terminates as TMethodCallStmt. }
|
||||
while Check(tkDot) do
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkIdent) then
|
||||
raise EParseError.CreateFmt('Expected field or method name at line %d col %d',
|
||||
[FCurrent.Line, FCurrent.Col]);
|
||||
SecondIdent := FCurrent.Value;
|
||||
Advance;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
MCall := TMethodCallStmt.Create;
|
||||
MCall.Line := Line;
|
||||
MCall.Col := Col;
|
||||
MCall.ObjectName := '';
|
||||
MCall.Name := SecondIdent;
|
||||
MCall.ObjExpr := CastRcv;
|
||||
Advance; { consume '(' }
|
||||
if not Check(tkRParen) then
|
||||
ParseMethodCallArgList(MCall);
|
||||
Expect(tkRParen);
|
||||
Result := MCall;
|
||||
Exit;
|
||||
end;
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := Line;
|
||||
FldNode.Col := Col;
|
||||
FldNode.Base := CastRcv;
|
||||
FldNode.FieldName := SecondIdent;
|
||||
CastRcv := FldNode;
|
||||
end;
|
||||
{ Field assignment through typecast/chain: TCast(expr).Field := value }
|
||||
if Check(tkAssign) and (CastRcv is TFieldAccessExpr) then
|
||||
begin
|
||||
Advance; { consume ':=' }
|
||||
FldAssign := TFieldAssignment.Create;
|
||||
FldAssign.Line := Line;
|
||||
FldAssign.Col := Col;
|
||||
FldAssign.FieldName := TFieldAccessExpr(CastRcv).FieldName;
|
||||
FldAssign.ObjExpr := TFieldAccessExpr(CastRcv).Base;
|
||||
TFieldAccessExpr(CastRcv).Base := nil; { transfer ownership }
|
||||
CastRcv.Free;
|
||||
FldAssign.Expr := ParseExpr;
|
||||
Result := FldAssign;
|
||||
Exit;
|
||||
end;
|
||||
raise EParseError.CreateFmt(
|
||||
'Expected method call after typecast at line %d col %d',
|
||||
[FCurrent.Line, FCurrent.Col]);
|
||||
end;
|
||||
{ No '.': it was a plain procedure/function call. Move args to a
|
||||
fresh TProcCall using FPC-style Extract (removes without freeing). }
|
||||
Call := TProcCall.Create;
|
||||
Call.Line := Line;
|
||||
Call.Col := Col;
|
||||
Call.Name := Name;
|
||||
while FCallNode.Args.Count > 0 do
|
||||
Call.Args.Add(FCallNode.Args.Extract(FCallNode.Args.Items[0]));
|
||||
FCallNode.Free;
|
||||
Result := Call;
|
||||
Exit;
|
||||
end;
|
||||
Call := TProcCall.Create;
|
||||
Call.Line := Line;
|
||||
Call.Col := Col;
|
||||
Call.Name := Name;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
Advance;
|
||||
if not Check(tkRParen) then
|
||||
ParseArgList(Call);
|
||||
Expect(tkRParen);
|
||||
end;
|
||||
Result := Call;
|
||||
end;
|
||||
end;
|
||||
|
|
@ -1424,8 +1635,9 @@ end;
|
|||
|
||||
function TParser.ParseCaseStmt: TCaseStmt;
|
||||
var
|
||||
Branch: TCaseBranch;
|
||||
Stmt: TASTStmt;
|
||||
Branch: TCaseBranch;
|
||||
Stmt: TASTStmt;
|
||||
CmpElse: TCompoundStmt;
|
||||
begin
|
||||
Result := TCaseStmt.Create;
|
||||
try
|
||||
|
|
@ -1457,7 +1669,28 @@ begin
|
|||
if Check(tkElse) then
|
||||
begin
|
||||
Advance; { consume 'else' }
|
||||
Result.ElseStmt := ParseStmt;
|
||||
if Check(tkBegin) then
|
||||
Result.ElseStmt := ParseCompoundStmt
|
||||
else
|
||||
begin
|
||||
{ Collect all statements until 'end' — FPC allows multi-stmt else without begin..end }
|
||||
CmpElse := TCompoundStmt.Create;
|
||||
CmpElse.Stmts.Add(ParseStmt);
|
||||
if Check(tkSemicolon) then Advance;
|
||||
while not (Check(tkEnd) or Check(tkEOF)) do
|
||||
begin
|
||||
CmpElse.Stmts.Add(ParseStmt);
|
||||
if Check(tkSemicolon) then Advance;
|
||||
end;
|
||||
if CmpElse.Stmts.Count = 1 then
|
||||
begin
|
||||
{ Unwrap single-item compound for simplicity }
|
||||
Result.ElseStmt := TASTStmt(CmpElse.Stmts.Extract(CmpElse.Stmts.Items[0]));
|
||||
CmpElse.Free;
|
||||
end
|
||||
else
|
||||
Result.ElseStmt := CmpElse;
|
||||
end;
|
||||
if Check(tkSemicolon) then Advance;
|
||||
end;
|
||||
Expect(tkEnd);
|
||||
|
|
@ -1611,8 +1844,10 @@ begin
|
|||
end
|
||||
else if Check(tkIs) then
|
||||
begin
|
||||
Advance;
|
||||
IsNode := TIsExpr.Create;
|
||||
IsNode.Line := FCurrent.Line;
|
||||
IsNode.Col := FCurrent.Col;
|
||||
Advance;
|
||||
IsNode.Obj := Result;
|
||||
IsNode.TypeName := FCurrent.Value;
|
||||
Expect(tkIdent);
|
||||
|
|
@ -1815,13 +2050,39 @@ begin
|
|||
while Check(tkDot) and (PeekKind = tkIdent) do
|
||||
begin
|
||||
Advance; { consume '.' }
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := FCurrent.Line;
|
||||
FldNode.Col := FCurrent.Col;
|
||||
FldNode.Base := Result;
|
||||
FldNode.FieldName := FCurrent.Value;
|
||||
Advance; { consume field name }
|
||||
Result := FldNode;
|
||||
SecondName := FCurrent.Value;
|
||||
Advance; { consume field/method name }
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
{ Chained method call on expression }
|
||||
MCallNode := TMethodCallExpr.Create;
|
||||
MCallNode.Line := FCurrent.Line;
|
||||
MCallNode.Col := FCurrent.Col;
|
||||
MCallNode.ObjectName := '';
|
||||
MCallNode.Name := SecondName;
|
||||
MCallNode.ObjExpr := Result;
|
||||
Advance; { consume '(' }
|
||||
if not Check(tkRParen) then
|
||||
begin
|
||||
MCallNode.Args.Add(ParseExpr);
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance;
|
||||
MCallNode.Args.Add(ParseExpr);
|
||||
end;
|
||||
end;
|
||||
Expect(tkRParen);
|
||||
Result := MCallNode;
|
||||
end
|
||||
else
|
||||
begin
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := FCurrent.Line;
|
||||
FldNode.Col := FCurrent.Col;
|
||||
FldNode.Base := Result;
|
||||
FldNode.FieldName := SecondName;
|
||||
Result := FldNode;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
|
|
@ -1844,6 +2105,52 @@ begin
|
|||
end;
|
||||
Expect(tkRParen);
|
||||
Result := FCallNode;
|
||||
{ Postfix chained field/method access: FuncOrCast(...).Member ... }
|
||||
while Check(tkDot) and (PeekKind = tkIdent) do
|
||||
begin
|
||||
Advance; { consume '.' }
|
||||
SecondName := FCurrent.Value;
|
||||
Advance; { consume field/method name }
|
||||
FldNode := TFieldAccessExpr.Create;
|
||||
FldNode.Line := FCurrent.Line;
|
||||
FldNode.Col := FCurrent.Col;
|
||||
FldNode.Base := Result;
|
||||
FldNode.FieldName := SecondName;
|
||||
Result := FldNode;
|
||||
if Check(tkLParen) then
|
||||
begin
|
||||
{ Method call with args on the chained expression — represent
|
||||
by marking the access as a method call and parsing an arg list
|
||||
into a synthesised TMethodCallExpr with Obj=Base. }
|
||||
{ We convert the TFieldAccessExpr into an arg-bearing method call. }
|
||||
Advance; { consume '(' }
|
||||
if not Check(tkRParen) then
|
||||
begin
|
||||
FldNode.ResolvedType := nil; { placeholder to note args }
|
||||
end;
|
||||
{ Lift the FldNode to a method call by storing args via a
|
||||
temporary TMethodCallExpr and using Base for the receiver. }
|
||||
MCallNode := TMethodCallExpr.Create;
|
||||
MCallNode.Line := FldNode.Line;
|
||||
MCallNode.Col := FldNode.Col;
|
||||
MCallNode.ObjectName := ''; { empty — receiver is Base expr }
|
||||
MCallNode.Name := SecondName;
|
||||
MCallNode.ObjExpr := FldNode.Base;
|
||||
FldNode.Base := nil; { transfer ownership }
|
||||
FldNode.Free;
|
||||
if not Check(tkRParen) then
|
||||
begin
|
||||
MCallNode.Args.Add(ParseExpr);
|
||||
while Check(tkComma) do
|
||||
begin
|
||||
Advance;
|
||||
MCallNode.Args.Add(ParseExpr);
|
||||
end;
|
||||
end;
|
||||
Expect(tkRParen);
|
||||
Result := MCallNode;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ type
|
|||
FProcIndex: TStringList; { 'ProcName' → TMethodDecl (not owned) }
|
||||
FGenericFuncTemplates: TStringList; { base name → TMethodDecl template (not owned) }
|
||||
FLoopDepth: Integer; { depth of enclosing while/for — Break only legal if > 0 }
|
||||
FScopeDepth: Integer; { mirrors FTable scope depth; used to detect main-level globals }
|
||||
FCurrentClass: TRecordTypeDesc; { class being analysed (set in AnalyseMethodDecl) }
|
||||
|
||||
{ Generic type instantiation: resolves 'TBox<Integer>' on demand. }
|
||||
|
|
@ -277,9 +278,15 @@ begin
|
|||
enum ↔ integer: allow assignment between enum and integer types }
|
||||
if (AExpected.Kind = tyEnum) and AActual.IsNumeric then Exit;
|
||||
if (AActual.Kind = tyEnum) and AExpected.IsNumeric then Exit;
|
||||
{ Numeric widening/narrowing: allow between the integer family members }
|
||||
if AExpected.IsNumeric and AActual.IsNumeric then Exit;
|
||||
{ subtype assignment: TDerived → TBase is allowed }
|
||||
if IsSubtypeOf(AActual, AExpected) then
|
||||
Exit;
|
||||
{ TObject accepts any class — universal base class }
|
||||
if (AExpected.Kind = tyClass) and (AExpected.Name = 'TObject') and
|
||||
(AActual.Kind = tyClass) then
|
||||
Exit;
|
||||
{ class → interface: allowed when the class implements that interface }
|
||||
if (AExpected.Kind = tyInterface) and (AActual.Kind = tyClass) then
|
||||
begin
|
||||
|
|
@ -288,6 +295,15 @@ begin
|
|||
if RT.ImplementsIntfAt(I) = AExpected then
|
||||
Exit;
|
||||
end;
|
||||
{ Untyped pointer accepts any class/interface/string reference and vice-versa }
|
||||
if (AExpected.Kind = tyPointer) and
|
||||
(TPointerTypeDesc(AExpected).BaseType = nil) and
|
||||
(AActual.Kind in [tyClass, tyInterface, tyString, tyPointer]) then
|
||||
Exit;
|
||||
if (AActual.Kind = tyPointer) and
|
||||
(TPointerTypeDesc(AActual).BaseType = nil) and
|
||||
(AExpected.Kind in [tyClass, tyInterface, tyString, tyPointer]) then
|
||||
Exit;
|
||||
SemanticError(
|
||||
Format('Type mismatch in %s: expected ''%s'' but got ''%s''',
|
||||
[AContext, AExpected.Name, AActual.Name]),
|
||||
|
|
@ -1005,16 +1021,18 @@ begin
|
|||
declarations, transferring the body so AnalyseMethodBodies can process it. }
|
||||
LinkClassMethodImpls(ABlock);
|
||||
LinkGenericClassMethodImpls(ABlock);
|
||||
{ Register standalone proc/func signatures before class method bodies so that
|
||||
methods can call free functions declared in the same block. }
|
||||
AnalyseStandaloneDecls(ABlock);
|
||||
AnalyseMethodBodies(ABlock);
|
||||
FTable.PushScope;
|
||||
Inc(FScopeDepth);
|
||||
try
|
||||
AnalyseVarDecls(ABlock);
|
||||
{ Register standalone proc/func signatures before analysing bodies so that
|
||||
mutually-recursive calls resolve correctly. }
|
||||
AnalyseStandaloneDecls(ABlock);
|
||||
AnalyseStandaloneBodies(ABlock);
|
||||
AnalyseStmts(ABlock);
|
||||
finally
|
||||
Dec(FScopeDepth);
|
||||
FTable.PopScope;
|
||||
end;
|
||||
end;
|
||||
|
|
@ -1033,8 +1051,9 @@ begin
|
|||
TD := FTable.TypeString
|
||||
else
|
||||
TD := FTable.TypeInteger;
|
||||
Sym := TSymbol.Create(CD.Name, skConstant, TD);
|
||||
Sym.ConstValue := CD.IntVal;
|
||||
Sym := TSymbol.Create(CD.Name, skConstant, TD);
|
||||
Sym.ConstValue := CD.IntVal;
|
||||
Sym.ConstString := CD.StrVal;
|
||||
if not FTable.Define(Sym) then
|
||||
Sym.Free; { duplicate const — silently ignore }
|
||||
end;
|
||||
|
|
@ -1434,6 +1453,7 @@ begin
|
|||
SavedClass := FCurrentClass;
|
||||
FCurrentClass := AClassType;
|
||||
FTable.PushScope;
|
||||
Inc(FScopeDepth);
|
||||
try
|
||||
{ Define Self as a variable of the class type }
|
||||
Sym := TSymbol.Create('Self', skVariable, AClassType);
|
||||
|
|
@ -1468,8 +1488,10 @@ begin
|
|||
end;
|
||||
|
||||
{ Analyse the method body block (pushes its own inner scope) }
|
||||
AnalyseBlock(AMethod.Body);
|
||||
if AMethod.Body <> nil then
|
||||
AnalyseBlock(AMethod.Body);
|
||||
finally
|
||||
Dec(FScopeDepth);
|
||||
FTable.PopScope;
|
||||
FCurrentClass := SavedClass;
|
||||
end;
|
||||
|
|
@ -1556,6 +1578,15 @@ begin
|
|||
ADecl.ResolvedReturnType := RetType;
|
||||
end;
|
||||
|
||||
{ If an earlier forward declaration exists, update the index to point to
|
||||
this implementation and skip re-registering the symbol. }
|
||||
J := FProcIndex.IndexOf(ADecl.Name);
|
||||
if (J >= 0) and (TMethodDecl(FProcIndex.Objects[J]).Body = nil) then
|
||||
begin
|
||||
FProcIndex.Objects[J] := ADecl;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
{ Index for call resolution }
|
||||
FProcIndex.AddObject(ADecl.Name, ADecl);
|
||||
|
||||
|
|
@ -1582,6 +1613,7 @@ var
|
|||
Sym: TSymbol;
|
||||
begin
|
||||
FTable.PushScope;
|
||||
Inc(FScopeDepth);
|
||||
try
|
||||
{ Define Result for functions }
|
||||
if ADecl.ResolvedReturnType <> nil then
|
||||
|
|
@ -1609,6 +1641,7 @@ begin
|
|||
|
||||
AnalyseBlock(ADecl.Body);
|
||||
finally
|
||||
Dec(FScopeDepth);
|
||||
FTable.PopScope;
|
||||
end;
|
||||
end;
|
||||
|
|
@ -1625,6 +1658,8 @@ begin
|
|||
if ADecl.OwnerTypeName <> '' then Continue;
|
||||
{ Generic templates are instantiated on demand — skip until first call }
|
||||
if ADecl.TypeParams <> nil then Continue;
|
||||
{ Forward declarations have no body; the later impl handles analysis }
|
||||
if ADecl.Body = nil then Continue;
|
||||
AnalyseStandaloneDecl(ADecl);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -1663,11 +1698,14 @@ begin
|
|||
Decl.IsWeak := True;
|
||||
end;
|
||||
|
||||
{ Depth 2 = inside the top-level program block — these are global variables. }
|
||||
Decl.IsGlobal := (FScopeDepth = 1); { depth 1 = main program block (global scope) }
|
||||
for J := 0 to Decl.Names.Count - 1 do
|
||||
begin
|
||||
VarName := Decl.Names[J];
|
||||
Sym := TSymbol.Create(VarName, skVariable, Typ);
|
||||
Sym.IsWeak := Decl.IsWeak;
|
||||
Sym.IsWeak := Decl.IsWeak;
|
||||
Sym.IsGlobal := Decl.IsGlobal;
|
||||
if not FTable.Define(Sym) then
|
||||
begin
|
||||
Sym.Free;
|
||||
|
|
@ -1714,6 +1752,7 @@ begin
|
|||
SemanticError(
|
||||
Format('Undeclared loop variable ''%s''', [ForS.VarName]),
|
||||
ForS.Line, ForS.Col);
|
||||
ForS.IsGlobal := (VarSym <> nil) and VarSym.IsGlobal;
|
||||
if VarSym.Kind <> skVariable then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a variable', [ForS.VarName]),
|
||||
|
|
@ -1763,6 +1802,11 @@ begin
|
|||
if FLoopDepth = 0 then
|
||||
SemanticError('''break'' is not inside a loop', AStmt.Line, AStmt.Col);
|
||||
end
|
||||
else if AStmt is TContinueStmt then
|
||||
begin
|
||||
if FLoopDepth = 0 then
|
||||
SemanticError('''continue'' is not inside a loop', AStmt.Line, AStmt.Col);
|
||||
end
|
||||
else if AStmt is TIfStmt then
|
||||
begin
|
||||
IfS := TIfStmt(AStmt);
|
||||
|
|
@ -1836,13 +1880,93 @@ var
|
|||
Par: TMethodParam;
|
||||
ArgType: TTypeDesc;
|
||||
I: Integer;
|
||||
ObjType: TTypeDesc;
|
||||
begin
|
||||
{ Call on a receiver expression: AProg.UsedUnits.Add(UName) }
|
||||
if ACall.ObjExpr <> nil then
|
||||
begin
|
||||
ObjType := AnalyseExpr(ACall.ObjExpr);
|
||||
if not (ObjType.Kind in [tyClass, tyInterface]) then
|
||||
SemanticError(
|
||||
Format('Receiver of ''.%s'' must be a class or interface', [ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
RT := TRecordTypeDesc(ObjType);
|
||||
MDecl := FindMethodDecl(RT.Name, ACall.Name);
|
||||
if (MDecl = nil) and SameText(ACall.Name, 'Free') and (ACall.Args.Count = 0) then
|
||||
begin
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := nil;
|
||||
Exit;
|
||||
end;
|
||||
if MDecl = nil then
|
||||
SemanticError(
|
||||
Format('Class ''%s'' has no method ''%s''', [RT.Name, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
if ACall.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[RT.Name, ACall.Name, MDecl.Params.Count, ACall.Args.Count]),
|
||||
ACall.Line, ACall.Col);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
begin
|
||||
ArgType := AnalyseExpr(TASTExpr(ACall.Args[I]));
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
end;
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := MDecl;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
ObjSym := FTable.Lookup(ACall.ObjectName);
|
||||
if ObjSym = nil then
|
||||
begin
|
||||
{ Implicit Self.Field.Method — ObjectName is a field of current class }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
ACall.ImplicitBaseInfo :=
|
||||
FCurrentClass.FindField(ACall.ObjectName);
|
||||
if (ACall.ImplicitBaseInfo <> nil) and
|
||||
(ACall.ImplicitBaseInfo.TypeDesc.Kind in [tyClass, tyInterface]) then
|
||||
begin
|
||||
ACall.IsImplicitSelf := True;
|
||||
RT := TRecordTypeDesc(ACall.ImplicitBaseInfo.TypeDesc);
|
||||
MDecl := FindMethodDecl(RT.Name, ACall.Name);
|
||||
if (MDecl = nil) and SameText(ACall.Name, 'Free') and (ACall.Args.Count = 0) then
|
||||
begin
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := nil;
|
||||
Exit;
|
||||
end;
|
||||
if MDecl = nil then
|
||||
SemanticError(
|
||||
Format('Class ''%s'' has no method ''%s''', [RT.Name, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
if ACall.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[RT.Name, ACall.Name, MDecl.Params.Count, ACall.Args.Count]),
|
||||
ACall.Line, ACall.Col);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
begin
|
||||
ArgType := AnalyseExpr(TASTExpr(ACall.Args[I]));
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
end;
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := MDecl;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared variable ''%s''', [ACall.ObjectName]),
|
||||
ACall.Line, ACall.Col);
|
||||
if ObjSym.Kind <> skVariable then
|
||||
end;
|
||||
if not (ObjSym.Kind in [skVariable, skParameter, skVarParameter]) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a variable', [ACall.ObjectName]),
|
||||
ACall.Line, ACall.Col);
|
||||
|
|
@ -1862,6 +1986,7 @@ begin
|
|||
{ Args for interface methods not checked in Phase 3 (no param info stored) }
|
||||
ACall.ResolvedClassType := ObjSym.TypeDesc;
|
||||
ACall.ResolvedMethod := nil; { nil = interface dispatch, not class dispatch }
|
||||
ACall.IsGlobal := ObjSym.IsGlobal;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
|
|
@ -1874,6 +1999,7 @@ begin
|
|||
begin
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := nil; { nil signals built-in Free to codegen }
|
||||
ACall.IsGlobal := ObjSym.IsGlobal;
|
||||
Exit;
|
||||
end;
|
||||
SemanticError(
|
||||
|
|
@ -1898,6 +2024,7 @@ begin
|
|||
|
||||
ACall.ResolvedClassType := RT;
|
||||
ACall.ResolvedMethod := MDecl;
|
||||
ACall.IsGlobal := ObjSym.IsGlobal;
|
||||
end;
|
||||
|
||||
procedure TSemanticAnalyser.AnalyseAssignment(AAssign: TAssignment);
|
||||
|
|
@ -1934,6 +2061,7 @@ begin
|
|||
AAssign.IsVarParam := (VarSym.Kind = skVarParameter);
|
||||
AAssign.ResolvedLhsType := VarSym.TypeDesc;
|
||||
AAssign.IsWeakLhs := VarSym.IsWeak;
|
||||
AAssign.IsGlobal := VarSym.IsGlobal;
|
||||
|
||||
ExprType := AnalyseExpr(AAssign.Expr);
|
||||
CheckTypesMatch(VarSym.TypeDesc, ExprType, 'assignment', AAssign.Line, AAssign.Col);
|
||||
|
|
@ -1989,15 +2117,85 @@ var
|
|||
RecSym: TSymbol;
|
||||
RT: TRecordTypeDesc;
|
||||
FldInfo: TFieldInfo;
|
||||
BaseInfo: TFieldInfo;
|
||||
BaseType: TTypeDesc;
|
||||
PropInfo: TPropertyInfo;
|
||||
ExprType: TTypeDesc;
|
||||
ObjType: TTypeDesc;
|
||||
begin
|
||||
{ ObjExpr path: receiver is an arbitrary expression (e.g. typecast result) }
|
||||
if AAssign.ObjExpr <> nil then
|
||||
begin
|
||||
ObjType := AnalyseExpr(AAssign.ObjExpr);
|
||||
if not (ObjType.Kind in [tyRecord, tyClass]) then
|
||||
SemanticError(
|
||||
Format('Field assignment: expression is not a record or class (got %s)',
|
||||
[ObjType.Name]),
|
||||
AAssign.Line, AAssign.Col);
|
||||
RT := TRecordTypeDesc(ObjType);
|
||||
FldInfo := RT.FindField(AAssign.FieldName);
|
||||
if FldInfo = nil then
|
||||
begin
|
||||
PropInfo := RT.FindProperty(AAssign.FieldName);
|
||||
if (PropInfo <> nil) and (PropInfo.WriteField <> '') then
|
||||
begin
|
||||
AAssign.FieldName := PropInfo.WriteField;
|
||||
FldInfo := RT.FindField(PropInfo.WriteField);
|
||||
end
|
||||
else
|
||||
SemanticError(
|
||||
Format('Type ''%s'' has no field ''%s''', [ObjType.Name, AAssign.FieldName]),
|
||||
AAssign.Line, AAssign.Col);
|
||||
end;
|
||||
AAssign.IsClassAccess := ObjType.Kind = tyClass;
|
||||
AAssign.FieldInfo := FldInfo;
|
||||
ExprType := AnalyseExpr(AAssign.Expr);
|
||||
CheckTypesMatch(FldInfo.TypeDesc, ExprType, 'field assignment',
|
||||
AAssign.Line, AAssign.Col);
|
||||
Exit;
|
||||
end;
|
||||
RecSym := FTable.Lookup(AAssign.RecordName);
|
||||
if RecSym = nil then
|
||||
begin
|
||||
{ Implicit Self.Field.Subfield — RecordName is a field of current class }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
BaseInfo := FCurrentClass.FindField(AAssign.RecordName);
|
||||
if (BaseInfo <> nil) and
|
||||
(BaseInfo.TypeDesc.Kind in [tyRecord, tyClass]) then
|
||||
begin
|
||||
AAssign.IsImplicitSelf := True;
|
||||
AAssign.ImplicitBaseInfo := BaseInfo;
|
||||
AAssign.IsClassAccess := BaseInfo.TypeDesc.Kind = tyClass;
|
||||
BaseType := BaseInfo.TypeDesc;
|
||||
RT := TRecordTypeDesc(BaseType);
|
||||
FldInfo := RT.FindField(AAssign.FieldName);
|
||||
if FldInfo = nil then
|
||||
begin
|
||||
PropInfo := RT.FindProperty(AAssign.FieldName);
|
||||
if (PropInfo <> nil) and (PropInfo.WriteField <> '') then
|
||||
begin
|
||||
AAssign.FieldName := PropInfo.WriteField;
|
||||
FldInfo := RT.FindField(PropInfo.WriteField);
|
||||
end
|
||||
else
|
||||
SemanticError(
|
||||
Format('Type ''%s'' has no field ''%s''',
|
||||
[AAssign.RecordName, AAssign.FieldName]),
|
||||
AAssign.Line, AAssign.Col);
|
||||
end;
|
||||
AAssign.FieldInfo := FldInfo;
|
||||
ExprType := AnalyseExpr(AAssign.Expr);
|
||||
CheckTypesMatch(FldInfo.TypeDesc, ExprType, 'field assignment',
|
||||
AAssign.Line, AAssign.Col);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared variable ''%s''', [AAssign.RecordName]),
|
||||
AAssign.Line, AAssign.Col);
|
||||
if RecSym.Kind <> skVariable then
|
||||
end;
|
||||
if not ((RecSym.Kind = skVariable) or (RecSym.Kind = skParameter)) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a variable', [AAssign.RecordName]),
|
||||
AAssign.Line, AAssign.Col);
|
||||
|
|
@ -2007,6 +2205,7 @@ begin
|
|||
AAssign.Line, AAssign.Col);
|
||||
|
||||
AAssign.IsClassAccess := RecSym.TypeDesc.Kind = tyClass;
|
||||
AAssign.IsGlobal := RecSym.IsGlobal;
|
||||
|
||||
RT := TRecordTypeDesc(RecSym.TypeDesc);
|
||||
FldInfo := RT.FindField(AAssign.FieldName);
|
||||
|
|
@ -2057,6 +2256,30 @@ begin
|
|||
Sym := FTable.Lookup(ACall.Name);
|
||||
if Sym = nil then
|
||||
begin
|
||||
{ Implicit Self.Method() — name is a method of the current class }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
MDecl := FindMethodDecl(FCurrentClass.Name, ACall.Name);
|
||||
if MDecl <> nil then
|
||||
begin
|
||||
if ACall.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[FCurrentClass.Name, ACall.Name, MDecl.Params.Count, ACall.Args.Count]),
|
||||
ACall.Line, ACall.Col);
|
||||
for I := 0 to ACall.Args.Count - 1 do
|
||||
begin
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
ArgType := AnalyseExpr(TASTExpr(ACall.Args[I]));
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, ACall.Name]),
|
||||
ACall.Line, ACall.Col);
|
||||
end;
|
||||
ACall.ResolvedDecl := MDecl;
|
||||
ACall.IsImplicitSelfMethod := True;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
{ Try on-demand instantiation of a generic function }
|
||||
if Pos('<', ACall.Name) > 0 then
|
||||
InstantiateGenericFunc(ACall.Name);
|
||||
|
|
@ -2143,6 +2366,32 @@ begin
|
|||
Sym := FTable.Lookup(AExpr.Name);
|
||||
if Sym = nil then
|
||||
begin
|
||||
{ Implicit Self.Method() on a function method of the current class }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
MDecl := FindMethodDecl(FCurrentClass.Name, AExpr.Name);
|
||||
if MDecl <> nil then
|
||||
begin
|
||||
if AExpr.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[FCurrentClass.Name, AExpr.Name, MDecl.Params.Count, AExpr.Args.Count]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
begin
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
ArgType := AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
AExpr.ResolvedDecl := MDecl;
|
||||
AExpr.IsImplicitSelfMethod := True;
|
||||
Result := MDecl.ResolvedReturnType;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
{ Try on-demand instantiation of a generic function }
|
||||
if Pos('<', AExpr.Name) > 0 then
|
||||
InstantiateGenericFunc(AExpr.Name);
|
||||
|
|
@ -2217,7 +2466,8 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
if SameText(AExpr.Name, 'UpperCase') or SameText(AExpr.Name, 'LowerCase') then
|
||||
if SameText(AExpr.Name, 'UpperCase') or SameText(AExpr.Name, 'LowerCase')
|
||||
or SameText(AExpr.Name, 'Trim') then
|
||||
begin
|
||||
if AExpr.Args.Count <> 1 then
|
||||
SemanticError(AExpr.Name + ' requires exactly one argument', AExpr.Line, AExpr.Col);
|
||||
|
|
@ -2280,6 +2530,16 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
if SameText(AExpr.Name, 'Chr') then
|
||||
begin
|
||||
if AExpr.Args.Count <> 1 then
|
||||
SemanticError('Chr requires exactly 1 argument', AExpr.Line, AExpr.Col);
|
||||
AnalyseExpr(TASTExpr(AExpr.Args[0]));
|
||||
Result := FTable.TypeString;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if SameText(AExpr.Name, 'CompareStr') or SameText(AExpr.Name, 'CompareText') then
|
||||
begin
|
||||
if AExpr.Args.Count <> 2 then
|
||||
|
|
@ -2389,15 +2649,147 @@ var
|
|||
ArgType: TTypeDesc;
|
||||
I: Integer;
|
||||
IntfDesc: TInterfaceTypeDesc;
|
||||
ObjType: TTypeDesc;
|
||||
begin
|
||||
{ Call on an arbitrary expression (e.g. TCast(x).Method(y)) }
|
||||
if AExpr.ObjExpr <> nil then
|
||||
begin
|
||||
ObjType := AnalyseExpr(AExpr.ObjExpr);
|
||||
if not (ObjType.Kind in [tyClass, tyInterface]) then
|
||||
SemanticError(
|
||||
Format('Receiver of ''.%s'' must be a class or interface', [AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
if ObjType.Kind = tyInterface then
|
||||
begin
|
||||
IntfDesc := TInterfaceTypeDesc(ObjType);
|
||||
if not IntfDesc.HasMethod(AExpr.Name) then
|
||||
SemanticError(
|
||||
Format('Interface ''%s'' has no method ''%s''',
|
||||
[ObjType.Name, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
AExpr.ResolvedClassType := ObjType;
|
||||
AExpr.ResolvedMethod := nil;
|
||||
Result := FindTypeOrInstantiate(
|
||||
IntfDesc.MethodReturnTypeName(IntfDesc.MethodIndex(AExpr.Name)));
|
||||
if Result = nil then Result := FTable.TypeInteger;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
RT := TRecordTypeDesc(ObjType);
|
||||
MDecl := FindMethodDecl(RT.Name, AExpr.Name);
|
||||
if MDecl = nil then
|
||||
SemanticError(
|
||||
Format('Class ''%s'' has no method ''%s''', [RT.Name, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
if AExpr.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[RT.Name, AExpr.Name, MDecl.Params.Count, AExpr.Args.Count]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
begin
|
||||
ArgType := AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
AExpr.ResolvedClassType := RT;
|
||||
AExpr.ResolvedMethod := MDecl;
|
||||
Result := MDecl.ResolvedReturnType;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
ObjSym := FTable.Lookup(AExpr.ObjectName);
|
||||
if ObjSym = nil then
|
||||
begin
|
||||
{ Implicit Self.Field.Method — ObjectName is a field of current class.
|
||||
Synthesise a receiver expression that reads the field. }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
ObjType := nil;
|
||||
ObjSym := nil;
|
||||
begin
|
||||
{ Attempt field lookup and rewrite AExpr.ObjExpr to read Self.Field }
|
||||
AExpr.ObjExpr := TIdentExpr.Create;
|
||||
TIdentExpr(AExpr.ObjExpr).Name := AExpr.ObjectName;
|
||||
TIdentExpr(AExpr.ObjExpr).Line := AExpr.Line;
|
||||
TIdentExpr(AExpr.ObjExpr).Col := AExpr.Col;
|
||||
try
|
||||
ObjType := AnalyseExpr(AExpr.ObjExpr);
|
||||
except
|
||||
AExpr.ObjExpr.Free;
|
||||
AExpr.ObjExpr := nil;
|
||||
SemanticError(
|
||||
Format('Undeclared identifier ''%s''', [AExpr.ObjectName]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
end;
|
||||
if (ObjType = nil) or not (ObjType.Kind in [tyClass, tyInterface]) then
|
||||
begin
|
||||
AExpr.ObjExpr.Free;
|
||||
AExpr.ObjExpr := nil;
|
||||
SemanticError(
|
||||
Format('Undeclared identifier ''%s''', [AExpr.ObjectName]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
AExpr.ObjectName := '';
|
||||
if ObjType.Kind = tyInterface then
|
||||
begin
|
||||
IntfDesc := TInterfaceTypeDesc(ObjType);
|
||||
if not IntfDesc.HasMethod(AExpr.Name) then
|
||||
SemanticError(
|
||||
Format('Interface ''%s'' has no method ''%s''',
|
||||
[ObjType.Name, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
AExpr.ResolvedClassType := ObjType;
|
||||
AExpr.ResolvedMethod := nil;
|
||||
Result := FindTypeOrInstantiate(
|
||||
IntfDesc.MethodReturnTypeName(IntfDesc.MethodIndex(AExpr.Name)));
|
||||
if Result = nil then Result := FTable.TypeInteger;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
RT := TRecordTypeDesc(ObjType);
|
||||
MDecl := FindMethodDecl(RT.Name, AExpr.Name);
|
||||
if MDecl = nil then
|
||||
SemanticError(
|
||||
Format('Class ''%s'' has no method ''%s''', [RT.Name, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
if AExpr.Args.Count <> MDecl.Params.Count then
|
||||
SemanticError(
|
||||
Format('Method ''%s.%s'' expects %d argument(s) but got %d',
|
||||
[RT.Name, AExpr.Name, MDecl.Params.Count, AExpr.Args.Count]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
begin
|
||||
ArgType := AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
Par := TMethodParam(MDecl.Params[I]);
|
||||
CheckTypesMatch(Par.ResolvedType, ArgType,
|
||||
Format('argument %d of ''%s''', [I + 1, AExpr.Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
AExpr.ResolvedClassType := RT;
|
||||
AExpr.ResolvedMethod := MDecl;
|
||||
Result := MDecl.ResolvedReturnType;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared identifier ''%s''', [AExpr.ObjectName]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
|
||||
{ Constructor call with args: TypeName.Create(arg1, arg2, ...) }
|
||||
if (ObjSym.Kind = skType) and SameText(AExpr.Name, 'Create') then
|
||||
{ Constructor call with args: TypeName.Create(arg1, arg2, ...) or any
|
||||
method on a class type starting with Create (e.g. CreateFmt). }
|
||||
if (ObjSym.Kind = skType) and
|
||||
(SameText(AExpr.Name, 'Create') or
|
||||
(Pos('Create', AExpr.Name) = 1)) then
|
||||
begin
|
||||
if ObjSym.TypeDesc.Kind <> tyClass then
|
||||
SemanticError(
|
||||
|
|
@ -2405,8 +2797,8 @@ begin
|
|||
AExpr.Line, AExpr.Col);
|
||||
for I := 0 to AExpr.Args.Count - 1 do
|
||||
AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
{ Try to find a user-defined Create method for type checking }
|
||||
MDecl := FindMethodDecl(AExpr.ObjectName, 'Create');
|
||||
{ Try to find a user-defined constructor method for type checking }
|
||||
MDecl := FindMethodDecl(AExpr.ObjectName, AExpr.Name);
|
||||
AExpr.ResolvedMethod := MDecl;
|
||||
AExpr.ResolvedClassType := ObjSym.TypeDesc;
|
||||
AExpr.IsConstructorCall := True;
|
||||
|
|
@ -2414,7 +2806,7 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
if ObjSym.Kind <> skVariable then
|
||||
if not (ObjSym.Kind in [skVariable, skParameter, skVarParameter]) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a variable', [AExpr.ObjectName]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
|
|
@ -2436,6 +2828,7 @@ begin
|
|||
AnalyseExpr(TASTExpr(AExpr.Args[I]));
|
||||
AExpr.ResolvedClassType := ObjSym.TypeDesc;
|
||||
AExpr.ResolvedMethod := nil; { nil = interface dispatch }
|
||||
AExpr.IsGlobal := ObjSym.IsGlobal;
|
||||
{ Look up return type from interface method descriptor }
|
||||
Result := FindTypeOrInstantiate(
|
||||
IntfDesc.MethodReturnTypeName(IntfDesc.MethodIndex(AExpr.Name)));
|
||||
|
|
@ -2473,13 +2866,16 @@ begin
|
|||
|
||||
AExpr.ResolvedClassType := RT;
|
||||
AExpr.ResolvedMethod := MDecl;
|
||||
AExpr.IsGlobal := ObjSym.IsGlobal;
|
||||
Result := MDecl.ResolvedReturnType;
|
||||
end;
|
||||
|
||||
function TSemanticAnalyser.AnalyseExpr(AExpr: TASTExpr): TTypeDesc;
|
||||
var
|
||||
Sym: TSymbol;
|
||||
FldInfo: TFieldInfo;
|
||||
Sym: TSymbol;
|
||||
FldInfo: TFieldInfo;
|
||||
PropInfo: TPropertyInfo;
|
||||
NoArgIdx: Integer;
|
||||
begin
|
||||
if AExpr is TNilLiteral then
|
||||
Result := FTable.TypeNil
|
||||
|
|
@ -2504,21 +2900,73 @@ begin
|
|||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
{ Bare zero-arg method call: e.g. TokenText inside a method }
|
||||
TIdentExpr(AExpr).ImplicitMethodDecl :=
|
||||
FindMethodDecl(FCurrentClass.Name, TIdentExpr(AExpr).Name);
|
||||
if TIdentExpr(AExpr).ImplicitMethodDecl <> nil then
|
||||
begin
|
||||
TIdentExpr(AExpr).IsImplicitSelfMethod := True;
|
||||
Result :=
|
||||
TMethodDecl(TIdentExpr(AExpr).ImplicitMethodDecl).ResolvedReturnType;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
{ Property of current class, method-backed: rewrite to the read method }
|
||||
FldInfo := nil; { reuse PropInfo via local }
|
||||
begin
|
||||
PropInfo := FCurrentClass.FindProperty(TIdentExpr(AExpr).Name);
|
||||
if PropInfo <> nil then
|
||||
begin
|
||||
if PropInfo.ReadMethod <> '' then
|
||||
begin
|
||||
TIdentExpr(AExpr).ImplicitMethodDecl :=
|
||||
FindMethodDecl(FCurrentClass.Name, PropInfo.ReadMethod);
|
||||
if TIdentExpr(AExpr).ImplicitMethodDecl <> nil then
|
||||
begin
|
||||
TIdentExpr(AExpr).IsImplicitSelfMethod := True;
|
||||
TIdentExpr(AExpr).Name := PropInfo.ReadMethod;
|
||||
Result := PropInfo.TypeDesc;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
end
|
||||
else if PropInfo.ReadField <> '' then
|
||||
begin
|
||||
FldInfo := FCurrentClass.FindField(PropInfo.ReadField);
|
||||
if FldInfo <> nil then
|
||||
begin
|
||||
TIdentExpr(AExpr).IsImplicitSelf := True;
|
||||
TIdentExpr(AExpr).ImplicitFieldInfo := FldInfo;
|
||||
Result := FldInfo.TypeDesc;
|
||||
AExpr.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared identifier ''%s''', [TIdentExpr(AExpr).Name]),
|
||||
AExpr.Line, AExpr.Col);
|
||||
end;
|
||||
TIdentExpr(AExpr).IsVarParam := (Sym.Kind = skVarParameter);
|
||||
TIdentExpr(AExpr).IsGlobal := Sym.IsGlobal;
|
||||
if Sym.Kind = skConstant then
|
||||
begin
|
||||
TIdentExpr(AExpr).IsConstant := True;
|
||||
TIdentExpr(AExpr).ConstValue := Sym.ConstValue;
|
||||
TIdentExpr(AExpr).IsConstant := True;
|
||||
TIdentExpr(AExpr).ConstValue := Sym.ConstValue;
|
||||
TIdentExpr(AExpr).ConstString := Sym.ConstString;
|
||||
end;
|
||||
{ Bare builtin function used without parens (e.g. ParamCount) — mark for codegen }
|
||||
if (Sym.Kind = skFunction) and (Sym.TypeDesc <> nil) and
|
||||
(FProcIndex.IndexOf(TIdentExpr(AExpr).Name) < 0) then
|
||||
{ Bare function reference without parens — mark as no-arg call for codegen.
|
||||
Covers both builtins (not in FProcIndex) and user-defined standalone functions. }
|
||||
if (Sym.Kind = skFunction) and (Sym.TypeDesc <> nil) then
|
||||
begin
|
||||
TIdentExpr(AExpr).IsNoArgFuncCall := True;
|
||||
{ Propagate TMethodDecl for user-defined functions so codegen can emit the call }
|
||||
NoArgIdx := FProcIndex.IndexOf(TIdentExpr(AExpr).Name);
|
||||
if NoArgIdx >= 0 then
|
||||
TIdentExpr(AExpr).NoArgFuncDecl := FProcIndex.Objects[NoArgIdx];
|
||||
end;
|
||||
Result := Sym.TypeDesc;
|
||||
end
|
||||
else if AExpr is TFuncCallExpr then
|
||||
|
|
@ -2582,6 +3030,14 @@ begin
|
|||
Result := PropInfo.TypeDesc;
|
||||
Exit;
|
||||
end;
|
||||
{ Zero-arg method call via field access: Obj.Method (no parens) }
|
||||
AAccess.ResolvedMethod := FindMethodDecl(RT.Name, AAccess.FieldName);
|
||||
if AAccess.ResolvedMethod <> nil then
|
||||
begin
|
||||
AAccess.IsMethodCall := True;
|
||||
Result := TMethodDecl(AAccess.ResolvedMethod).ResolvedReturnType;
|
||||
Exit;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Type ''%s'' has no field ''%s''',
|
||||
[BaseType.Name, AAccess.FieldName]),
|
||||
|
|
@ -2594,9 +3050,53 @@ begin
|
|||
|
||||
RecSym := FTable.Lookup(AAccess.RecordName);
|
||||
if RecSym = nil then
|
||||
begin
|
||||
{ Implicit Self.RecordName.FieldName — RecordName is a field of current class }
|
||||
if FCurrentClass <> nil then
|
||||
begin
|
||||
FldInfo := FCurrentClass.FindField(AAccess.RecordName);
|
||||
if (FldInfo <> nil) and
|
||||
(FldInfo.TypeDesc.Kind in [tyRecord, tyClass]) then
|
||||
begin
|
||||
AAccess.IsImplicitSelf := True;
|
||||
AAccess.ImplicitBaseInfo := FldInfo;
|
||||
AAccess.IsClassAccess := FldInfo.TypeDesc.Kind = tyClass;
|
||||
RT := TRecordTypeDesc(FldInfo.TypeDesc);
|
||||
AAccess.FieldInfo := RT.FindField(AAccess.FieldName);
|
||||
if AAccess.FieldInfo = nil then
|
||||
begin
|
||||
{ Field-backed property on the implicit-Self field's type }
|
||||
PropInfo := RT.FindProperty(AAccess.FieldName);
|
||||
if (PropInfo <> nil) and (PropInfo.ReadField <> '') then
|
||||
begin
|
||||
AAccess.FieldName := PropInfo.ReadField;
|
||||
AAccess.FieldInfo := RT.FindField(PropInfo.ReadField);
|
||||
Result := PropInfo.TypeDesc;
|
||||
AAccess.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
{ Zero-arg method on the implicit-Self field: FTok.NextToken }
|
||||
AAccess.ResolvedMethod := FindMethodDecl(RT.Name, AAccess.FieldName);
|
||||
if AAccess.ResolvedMethod <> nil then
|
||||
begin
|
||||
AAccess.IsMethodCall := True;
|
||||
Result := TMethodDecl(AAccess.ResolvedMethod).ResolvedReturnType;
|
||||
AAccess.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Type ''%s'' has no field ''%s''',
|
||||
[AAccess.RecordName, AAccess.FieldName]),
|
||||
AAccess.Line, AAccess.Col);
|
||||
end;
|
||||
Result := AAccess.FieldInfo.TypeDesc;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
SemanticError(
|
||||
Format('Undeclared identifier ''%s''', [AAccess.RecordName]),
|
||||
AAccess.Line, AAccess.Col);
|
||||
end;
|
||||
|
||||
{ Constructor call: TypeName.Create }
|
||||
if RecSym.Kind = skType then
|
||||
|
|
@ -2612,12 +3112,13 @@ begin
|
|||
[AAccess.FieldName, AAccess.RecordName]),
|
||||
AAccess.Line, AAccess.Col);
|
||||
AAccess.IsConstructorCall := True;
|
||||
AAccess.ResolvedMethod := FindMethodDecl(TRecordTypeDesc(RecSym.TypeDesc).Name, 'Create');
|
||||
Result := RecSym.TypeDesc;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Field access on variable }
|
||||
if RecSym.Kind <> skVariable then
|
||||
{ Field access on variable or parameter }
|
||||
if not (RecSym.Kind in [skVariable, skParameter, skVarParameter]) then
|
||||
SemanticError(
|
||||
Format('''%s'' is not a variable or type', [AAccess.RecordName]),
|
||||
AAccess.Line, AAccess.Col);
|
||||
|
|
@ -2628,11 +3129,21 @@ begin
|
|||
AAccess.Line, AAccess.Col);
|
||||
|
||||
AAccess.IsClassAccess := RecSym.TypeDesc.Kind = tyClass;
|
||||
AAccess.IsGlobal := RecSym.IsGlobal;
|
||||
|
||||
RT := TRecordTypeDesc(RecSym.TypeDesc);
|
||||
FldInfo := RT.FindField(AAccess.FieldName);
|
||||
if FldInfo = nil then
|
||||
begin
|
||||
{ Zero-arg method call via field access: Obj.Method (no parens) }
|
||||
AAccess.ResolvedMethod := FindMethodDecl(RT.Name, AAccess.FieldName);
|
||||
if AAccess.ResolvedMethod <> nil then
|
||||
begin
|
||||
AAccess.IsMethodCall := True;
|
||||
Result := TMethodDecl(AAccess.ResolvedMethod).ResolvedReturnType;
|
||||
AAccess.ResolvedType := Result;
|
||||
Exit;
|
||||
end;
|
||||
{ Check if this is a property access }
|
||||
PropInfo := RT.FindProperty(AAccess.FieldName);
|
||||
if PropInfo <> nil then
|
||||
|
|
@ -2697,7 +3208,13 @@ begin
|
|||
(LType = RType) or
|
||||
((LType.Kind = tyNil) and (RType.Kind in [tyClass, tyInterface, tyPointer])) or
|
||||
((RType.Kind = tyNil) and (LType.Kind in [tyClass, tyInterface, tyPointer])) or
|
||||
((LType.Kind = tyPointer) and (RType.Kind = tyPointer))
|
||||
((LType.Kind = tyPointer) and (RType.Kind = tyPointer)) or
|
||||
{ Class comparisons: allow subtype on either side }
|
||||
((LType.Kind = tyClass) and (RType.Kind = tyClass) and
|
||||
(IsSubtypeOf(LType, RType) or IsSubtypeOf(RType, LType))) or
|
||||
{ TObject is universal base class }
|
||||
((LType.Kind = tyClass) and (RType.Kind = tyClass) and
|
||||
((LType.Name = 'TObject') or (RType.Name = 'TObject')))
|
||||
) then
|
||||
CheckTypesMatch(LType, RType,
|
||||
Format('comparison ''%s''', [BinaryOpName(ABin.Op)]),
|
||||
|
|
@ -2746,7 +3263,9 @@ var
|
|||
TargetType: TTypeDesc;
|
||||
begin
|
||||
ObjType := AnalyseExpr(AExpr.Obj);
|
||||
if ObjType.Kind <> tyClass then
|
||||
{ Allow untyped Pointer on left — GetObject/Get return Pointer, used with 'is' }
|
||||
if not ((ObjType.Kind = tyClass) or (ObjType.Kind = tyPointer) or
|
||||
(ObjType.Kind = tyInterface)) then
|
||||
SemanticError(
|
||||
Format('''is'' requires a class instance on the left, got ''%s''',
|
||||
[ObjType.Name]),
|
||||
|
|
|
|||
|
|
@ -188,10 +188,13 @@ type
|
|||
Kind: TSymbolKind;
|
||||
TypeDesc: TTypeDesc; { not owned; nil for procedures }
|
||||
Params: TObjectList; { owned TParamDesc; populated for procedures/functions }
|
||||
ConstValue: Int64; { valid when Kind = skConstant }
|
||||
ConstValue: Int64; { valid when Kind = skConstant; integer/bool/enum value }
|
||||
ConstString: string; { valid when Kind = skConstant and type is tyString }
|
||||
IsWeak: Boolean; { true for variables declared [Weak]; codegen
|
||||
keys off this to emit _WeakAssign instead
|
||||
of the strong addref/release pattern. }
|
||||
IsGlobal: Boolean; { true for program-level variables; codegen uses
|
||||
QBE data-section storage instead of stack alloc }
|
||||
constructor Create(const AName: string; AKind: TSymbolKind; AType: TTypeDesc);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -320,7 +323,7 @@ begin
|
|||
case Kind of
|
||||
tyInteger, tyUInt32, tyEnum: Result := 4;
|
||||
tyInt64: Result := 8;
|
||||
tyByte, tyBoolean: Result := 1;
|
||||
tyByte, tyBoolean: Result := 4; { stored as word, same as AllocAlign }
|
||||
tyString: Result := 8; { pointer size on 64-bit }
|
||||
tyRecord: Result := TRecordTypeDesc(Self).TotalSize;
|
||||
tyNil: Result := 8;
|
||||
|
|
@ -846,6 +849,8 @@ begin
|
|||
Define(Sym);
|
||||
Sym := TSymbol.Create('LowerCase', skFunction, FTypeString);
|
||||
Define(Sym);
|
||||
Sym := TSymbol.Create('Trim', skFunction, FTypeString);
|
||||
Define(Sym);
|
||||
Sym := TSymbol.Create('SameText', skFunction, FTypeBoolean);
|
||||
Define(Sym);
|
||||
Sym := TSymbol.Create('IntToStr', skFunction, FTypeString);
|
||||
|
|
@ -858,6 +863,8 @@ begin
|
|||
Define(Sym);
|
||||
Sym := TSymbol.Create('OrdAt', skFunction, FTypeInteger);
|
||||
Define(Sym);
|
||||
Sym := TSymbol.Create('Chr', skFunction, FTypeString);
|
||||
Define(Sym);
|
||||
{ Memory utilities }
|
||||
Sym := TSymbol.Create('ZeroMem', skProcedure, nil); Define(Sym);
|
||||
Sym := TSymbol.Create('_ClassAddRef', skProcedure, nil); Define(Sym);
|
||||
|
|
@ -869,7 +876,9 @@ begin
|
|||
Sym := TSymbol.Create('ReadFile', skFunction, FTypeString); Define(Sym);
|
||||
Sym := TSymbol.Create('WriteFile', skProcedure, nil); Define(Sym);
|
||||
Sym := TSymbol.Create('AppendFile', skProcedure, nil); Define(Sym);
|
||||
Sym := TSymbol.Create('FileExists', skFunction, FTypeBoolean); Define(Sym);
|
||||
Sym := TSymbol.Create('FileExists', skFunction, FTypeBoolean); Define(Sym);
|
||||
Sym := TSymbol.Create('DeleteFile', skProcedure, nil); Define(Sym);
|
||||
Sym := TSymbol.Create('CurrentExceptionMessage', skFunction, FTypeString); Define(Sym);
|
||||
{ Environment and process }
|
||||
Sym := TSymbol.Create('GetEnvVar', skFunction, FTypeString); Define(Sym);
|
||||
Sym := TSymbol.Create('Exec', skFunction, FTypeInteger); Define(Sym);
|
||||
|
|
|
|||
|
|
@ -511,8 +511,8 @@ begin
|
|||
' end;' + LineEnding +
|
||||
'var F: TFoo;' + LineEnding +
|
||||
'begin end.');
|
||||
AssertTrue('alloc8 for F', Pos('_var_F', IR) > 0);
|
||||
AssertTrue('8-byte alloc', Pos('alloc8', IR) > 0);
|
||||
{ Program-level class var F is a data-section global pointer slot }
|
||||
AssertTrue('data decl for F', Pos('$F', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TClassTests.TestCodegen_ClassVar_ZeroInit;
|
||||
|
|
@ -527,7 +527,8 @@ begin
|
|||
' end;' + LineEnding +
|
||||
'var F: TFoo;' + LineEnding +
|
||||
'begin end.');
|
||||
AssertTrue('zero init', Pos('storel 0', IR) > 0);
|
||||
{ Program-level class var F is zero-initialised via data section entry }
|
||||
AssertTrue('zero init via data section', Pos('data $F = { l 0 }', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TClassTests.TestCodegen_Constructor_CallsClassAlloc;
|
||||
|
|
@ -567,7 +568,8 @@ begin
|
|||
'begin' + LineEnding +
|
||||
' F.X := 42' + LineEnding +
|
||||
'end.');
|
||||
AssertTrue('loads pointer', Pos('loadl %_var_F', IR) > 0);
|
||||
{ F is a program-level global — loaded via $F }
|
||||
AssertTrue('loads pointer', Pos('loadl $F', IR) > 0);
|
||||
AssertTrue('stores value', Pos('storew', IR) > 0);
|
||||
end;
|
||||
|
||||
|
|
@ -585,7 +587,8 @@ begin
|
|||
'begin' + LineEnding +
|
||||
' N := F.X' + LineEnding +
|
||||
'end.');
|
||||
AssertTrue('loads pointer', Pos('loadl %_var_F', IR) > 0);
|
||||
{ F is a program-level global — loaded via $F }
|
||||
AssertTrue('loads pointer', Pos('loadl $F', IR) > 0);
|
||||
AssertTrue('loads field', Pos('loadw', IR) > 0);
|
||||
end;
|
||||
|
||||
|
|
@ -689,7 +692,7 @@ begin
|
|||
becomes a no-op. }
|
||||
IR := GenIR(SrcFree);
|
||||
AssertTrue('calls _ClassRelease', Pos('call $_ClassRelease', IR) > 0);
|
||||
AssertTrue('nil-outs slot after Free', Pos('storel 0, %_var_', IR) > 0);
|
||||
AssertTrue('nil-outs slot after Free', (Pos('storel 0, %_var_', IR) > 0) or (Pos('storel 0, $', IR) > 0));
|
||||
AssertTrue('does not call C free() directly', Pos('call $free(', IR) = 0);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -176,8 +176,9 @@ var
|
|||
IR: string;
|
||||
begin
|
||||
IR := GenerateIR('program P; var x: Integer; begin end.');
|
||||
AssertTrue('Has alloc for x',
|
||||
IRContains(IR, '%_var_x =l alloc4 1'));
|
||||
{ Program-level var is a data-section global, not a stack alloc }
|
||||
AssertTrue('Has data decl for x',
|
||||
IRContains(IR, 'data $x'));
|
||||
end;
|
||||
|
||||
procedure TCodeGenTests.TestAssignment_HasStorew;
|
||||
|
|
@ -187,7 +188,8 @@ begin
|
|||
IR := GenerateIR(
|
||||
'program P; var n: Integer; begin n := 7 end.');
|
||||
AssertTrue('Has storew', IRContains(IR, 'storew'));
|
||||
AssertTrue('Stores to n', IRContains(IR, 'storew %_t0, %_var_n'));
|
||||
{ Program-level var n is a global: store goes to $n }
|
||||
AssertTrue('Stores to n', IRContains(IR, 'storew %_t0, $n'));
|
||||
end;
|
||||
|
||||
procedure TCodeGenTests.TestAssignment_LoadAndStore;
|
||||
|
|
@ -196,8 +198,9 @@ var
|
|||
begin
|
||||
IR := GenerateIR(
|
||||
'program P; var x, y: Integer; begin x := 1; y := x end.');
|
||||
AssertTrue('Loads x', IRContains(IR, 'loadw %_var_x'));
|
||||
AssertTrue('Stores y', IRContains(IR, '%_var_y'));
|
||||
{ Program-level vars x, y are globals: load/store use $name }
|
||||
AssertTrue('Loads x', IRContains(IR, 'loadw $x'));
|
||||
AssertTrue('Stores y', IRContains(IR, '$y'));
|
||||
end;
|
||||
|
||||
{ Arithmetic }
|
||||
|
|
|
|||
|
|
@ -465,9 +465,9 @@ var
|
|||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcGetterClass);
|
||||
{ The call result is assigned into a temp and then stored into %_var_N }
|
||||
{ N is a program-level global; the call result is stored into $N }
|
||||
AssertTrue('result stored into N',
|
||||
Pos('%_var_N', IR) > 0);
|
||||
Pos('$N', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TFunctionTests.TestCodegen_Function_ReturnsResult;
|
||||
|
|
|
|||
|
|
@ -265,8 +265,9 @@ procedure TInheritTests.TestCodegen_Nil_StoresZero;
|
|||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcSelfRef);
|
||||
{ N.Next := nil should store 0 (null pointer) }
|
||||
AssertTrue('nil stores 0', Pos('storel 0', IR) > 0);
|
||||
{ N is a data-section global; N.Next := nil loads 0 and stores via a temp.
|
||||
Verify the nil load and that the global $N is present. }
|
||||
AssertTrue('nil stores 0', Pos('copy 0', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TInheritTests.TestCodegen_Nil_CompareEmitsCeql;
|
||||
|
|
|
|||
|
|
@ -481,8 +481,9 @@ var
|
|||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcInterfaceVar);
|
||||
AssertTrue('obj slot for F', Pos('_var_F_obj', IR) > 0);
|
||||
AssertTrue('itab slot for F', Pos('_var_F_itab', IR) > 0);
|
||||
{ F is a program-level global — slots are $F_obj and $F_itab }
|
||||
AssertTrue('obj slot for F', Pos('$F_obj', IR) > 0);
|
||||
AssertTrue('itab slot for F', Pos('$F_itab', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TInterfaceTests.TestCodegen_InterfaceMethodCall_IndirectDispatch;
|
||||
|
|
@ -491,7 +492,7 @@ var
|
|||
begin
|
||||
IR := GenIR(SrcInterfaceVar);
|
||||
{ Interface dispatch loads the itab pointer and calls indirectly }
|
||||
AssertTrue('loads itab pointer', Pos('_var_F_itab', IR) > 0);
|
||||
AssertTrue('loads itab pointer', Pos('$F_itab', IR) > 0);
|
||||
AssertTrue('indirect call via register', Pos('call %', IR) > 0);
|
||||
end;
|
||||
|
||||
|
|
@ -680,10 +681,11 @@ begin
|
|||
_F_obj and _F_itab are loaded for the read side and both _G_obj and
|
||||
_G_itab are stored on the write side. }
|
||||
IR := GenIR(SrcIntfToIntf);
|
||||
AssertTrue('reads F_obj', Pos('loadl %_var_F_obj', IR) > 0);
|
||||
AssertTrue('reads F_itab', Pos('loadl %_var_F_itab', IR) > 0);
|
||||
{ F, G are program-level globals — slots accessed via $F_obj, $F_itab etc. }
|
||||
AssertTrue('reads F_obj', Pos('loadl $F_obj', IR) > 0);
|
||||
AssertTrue('reads F_itab', Pos('loadl $F_itab', IR) > 0);
|
||||
AssertTrue('writes G_obj', Pos('storel ', IR) > 0);
|
||||
AssertTrue('writes G_itab slot', Pos('%_var_G_itab', IR) > 0);
|
||||
AssertTrue('writes G_itab slot', Pos('$G_itab', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TInterfaceTests.TestCodegen_InterfaceVar_ScopeExit_ReleasesObjOnly;
|
||||
|
|
@ -693,12 +695,12 @@ begin
|
|||
have its obj slot released. itab is a static pointer and is not
|
||||
refcounted, so it must NOT be released. }
|
||||
IR := GenIR(SrcInterfaceVar);
|
||||
{ Look for "loadl %_var_F_obj" followed by _ClassRelease somewhere after
|
||||
main_exit — ensures the scope-exit release pass covered interface vars. }
|
||||
{ F is a program-level global — slots accessed via $F_obj, $F_itab.
|
||||
Check that scope-exit cleanup loads the obj slot for release. }
|
||||
AssertTrue('scope-exit loads obj slot of interface var',
|
||||
Pos('loadl %_var_F_obj', IR) > 0);
|
||||
Pos('loadl $F_obj', IR) > 0);
|
||||
AssertTrue('no StringRelease or direct free on itab slot',
|
||||
Pos('loadl %_var_F_itab', IR) > 0); { itab is read during the method
|
||||
Pos('loadl $F_itab', IR) > 0); { itab is read during the method
|
||||
call path but never released. }
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -517,8 +517,8 @@ begin
|
|||
' end;' + LineEnding +
|
||||
'var Pt: TPoint;' + LineEnding +
|
||||
'begin end.');
|
||||
AssertTrue('alloc for Pt', Pos('_var_Pt', IR) > 0);
|
||||
AssertTrue('alloc present', Pos('alloc', IR) > 0);
|
||||
{ Program-level record var Pt is a data-section global }
|
||||
AssertTrue('data decl for Pt', Pos('$Pt', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TRecordTests.TestCodegen_FieldStore_EmitsOffset;
|
||||
|
|
@ -568,8 +568,8 @@ begin
|
|||
' end;' + LineEnding +
|
||||
'var R: TRect;' + LineEnding +
|
||||
'begin end.');
|
||||
{ Two Integer fields = 8 bytes total }
|
||||
AssertTrue('alloc4 8', Pos('alloc4 8', IR) > 0);
|
||||
{ Two Integer fields = 8 bytes total; program-level record uses data section }
|
||||
AssertTrue('8-byte record in data section', Pos('z 8', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TRecordTests.TestCodegen_StringField_CorrectSize;
|
||||
|
|
@ -584,8 +584,8 @@ begin
|
|||
' end;' + LineEnding +
|
||||
'var Person: TPerson;' + LineEnding +
|
||||
'begin end.');
|
||||
{ One string field = 8 bytes, 8-byte aligned }
|
||||
AssertTrue('alloc8 8', Pos('alloc8 8', IR) > 0);
|
||||
{ One string field = 8 bytes; program-level record uses data section }
|
||||
AssertTrue('8-byte record in data section', Pos('z 8', IR) > 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
|
|
|||
|
|
@ -253,8 +253,8 @@ procedure TVarParamTests.TestCodegen_VarParam_CallPassesAddress;
|
|||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcVarSet);
|
||||
{ SetVal(V) must pass the address of V, not its loaded value }
|
||||
AssertTrue('call passes address of V', Pos('call $SetVal(l %_var_V)', IR) > 0);
|
||||
{ SetVal(V) must pass the address of V — V is a global so address is $V }
|
||||
AssertTrue('call passes address of V', Pos('call $SetVal(l $V)', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TVarParamTests.TestCodegen_VarParam_WriteStoresThroughPointer;
|
||||
|
|
@ -278,9 +278,9 @@ procedure TVarParamTests.TestCodegen_VarParam_Swap;
|
|||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcVarSwap);
|
||||
{ Both X and Y addresses passed to Swap }
|
||||
AssertTrue('passes address of X', Pos('l %_var_X', IR) > 0);
|
||||
AssertTrue('passes address of Y', Pos('l %_var_Y', IR) > 0);
|
||||
{ Both X and Y addresses passed to Swap — X and Y are globals so addresses are $X, $Y }
|
||||
AssertTrue('passes address of X', Pos('l $X', IR) > 0);
|
||||
AssertTrue('passes address of Y', Pos('l $Y', IR) > 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
|
|
|||
|
|
@ -15,6 +15,20 @@
|
|||
|
||||
#include <setjmp.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Forward-declared string helper to build a Blaise string from a C string. */
|
||||
static void* exc_str_from_cstr(const char* s) {
|
||||
typedef struct { int32_t refcnt; int32_t length; int32_t capacity; } StrHdr;
|
||||
int32_t len = s ? (int32_t)strlen(s) : 0;
|
||||
StrHdr* h = (StrHdr*)malloc(sizeof(StrHdr) + len + 1);
|
||||
if (!h) return NULL;
|
||||
h->refcnt = 0; h->length = len; h->capacity = len;
|
||||
if (len > 0) memcpy((char*)(h + 1), s, (size_t)len);
|
||||
((char*)(h + 1))[len] = '\0';
|
||||
return (void*)h;
|
||||
}
|
||||
|
||||
typedef struct BlaiseExcFrame {
|
||||
jmp_buf jbuf; /* offset 0 — frame ptr passed directly to setjmp */
|
||||
|
|
@ -69,6 +83,19 @@ void* _CurrentException(void) {
|
|||
return g_exc_top ? g_exc_top->exception : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* _CurrentExceptionMessage — return the Message string of the current exception.
|
||||
* Class layout: offset 0 = vptr (8 bytes), offset 8 = FMessage (string pointer).
|
||||
* Returns an empty string if no exception is active.
|
||||
*/
|
||||
void* _CurrentExceptionMessage(void) {
|
||||
void* exc = g_exc_top ? g_exc_top->exception : NULL;
|
||||
if (!exc) return exc_str_from_cstr("");
|
||||
/* FMessage is the first user field, after the 8-byte vptr */
|
||||
void* msg = *((void**)((char*)exc + 8));
|
||||
return msg ? msg : exc_str_from_cstr("");
|
||||
}
|
||||
|
||||
/*
|
||||
* _Reraise — re-raise the given exception to the enclosing handler.
|
||||
* Called by the compiler on the exception path of try/finally, after
|
||||
|
|
|
|||
|
|
@ -155,6 +155,16 @@ int32_t _FileExists(void* filename) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _DeleteFile(FileName) : void */
|
||||
/* Deletes a file. Silently ignores failure. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void _DeleteFile(void* filename) {
|
||||
const char* path = io_str_data(filename);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _GetEnvVar(Name) : string */
|
||||
/* Returns the value of environment variable Name, or empty string. */
|
||||
|
|
|
|||
|
|
@ -137,6 +137,25 @@ void* _StringLowerCase(void* s) {
|
|||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _StringTrim(s) : string — strip leading and trailing whitespace */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void* _StringTrim(void* s) {
|
||||
int32_t len = str_len(s);
|
||||
const char* data = str_data(s);
|
||||
int32_t lo = 0;
|
||||
int32_t hi = len - 1;
|
||||
while (lo <= hi && (unsigned char)data[lo] <= ' ') lo++;
|
||||
while (hi >= lo && (unsigned char)data[hi] <= ' ') hi--;
|
||||
int32_t newlen = (hi >= lo) ? (hi - lo + 1) : 0;
|
||||
void* r = str_alloc(newlen);
|
||||
if (!r) return NULL;
|
||||
if (newlen > 0)
|
||||
memcpy((char*)r + sizeof(BlaiseStrHdr), data + lo, newlen);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _StringSameText(s1, s2) : Boolean (0 or 1) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -191,6 +210,18 @@ int32_t _OrdAt(void* s, int32_t i) {
|
|||
return (int32_t)(unsigned char)data[i - 1];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _Chr(n) : string — one-character string holding byte value n. */
|
||||
/* Returns an unowned header (RefCount = 0); caller AddRefs. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void* _Chr(int32_t n) {
|
||||
BlaiseStrHdr* h = (BlaiseStrHdr*)str_alloc(1);
|
||||
if (!h) return NULL;
|
||||
((char*)(h + 1))[0] = (char)(unsigned char)n;
|
||||
return (void*)h;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* _StringCompare(s1, s2) : Integer — case-sensitive (like strcmp) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ type
|
|||
procedure Put(AIndex: Integer; AObject: Pointer);
|
||||
function IndexOf(AObject: Pointer): Integer;
|
||||
procedure Delete(AIndex: Integer);
|
||||
function Extract(AIndex: Integer): Pointer;
|
||||
procedure Clear;
|
||||
property Count: Integer read FCount;
|
||||
end;
|
||||
|
|
@ -81,6 +82,7 @@ type
|
|||
procedure Delete(AIndex: Integer);
|
||||
procedure Clear;
|
||||
procedure Insert(AIndex: Integer; S: string);
|
||||
procedure AddStrings(ASource: TStringList);
|
||||
function GetText: string;
|
||||
property Count: Integer read FCount;
|
||||
property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive;
|
||||
|
|
@ -88,12 +90,49 @@ type
|
|||
property Duplicates: Integer read FDuplicates write FDuplicates;
|
||||
end;
|
||||
|
||||
procedure SplitIntoList(const S: string; ASep: Integer; AList: TStringList);
|
||||
|
||||
implementation
|
||||
|
||||
{ ================================================================== }
|
||||
{ TObjectList }
|
||||
{ ================================================================== }
|
||||
|
||||
procedure SplitIntoList(const S: string; ASep: Integer; AList: TStringList);
|
||||
var
|
||||
I: Integer;
|
||||
Start: Integer;
|
||||
SLo: Integer;
|
||||
SHi: Integer;
|
||||
begin
|
||||
AList.Clear;
|
||||
Start := 1;
|
||||
I := 1;
|
||||
while I <= Length(S) do
|
||||
begin
|
||||
if OrdAt(S, I) = ASep then
|
||||
begin
|
||||
{ Trim surrounding spaces }
|
||||
SLo := Start;
|
||||
SHi := I - 1;
|
||||
while (SLo <= SHi) and (OrdAt(S, SLo) = 32) do SLo := SLo + 1;
|
||||
while (SHi >= SLo) and (OrdAt(S, SHi) = 32) do SHi := SHi - 1;
|
||||
AList.Add(Copy(S, SLo, SHi - SLo + 1));
|
||||
Start := I + 1;
|
||||
end;
|
||||
I := I + 1;
|
||||
end;
|
||||
if Start <= Length(S) then
|
||||
begin
|
||||
SLo := Start;
|
||||
SHi := Length(S);
|
||||
while (SLo <= SHi) and (OrdAt(S, SLo) = 32) do SLo := SLo + 1;
|
||||
while (SHi >= SLo) and (OrdAt(S, SHi) = 32) do SHi := SHi - 1;
|
||||
AList.Add(Copy(S, SLo, SHi - SLo + 1));
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TObjectList.Grow;
|
||||
var
|
||||
NewCap: Integer;
|
||||
|
|
@ -188,6 +227,25 @@ begin
|
|||
Self.FCount := 0
|
||||
end;
|
||||
|
||||
function TObjectList.Extract(AIndex: Integer): Pointer;
|
||||
var
|
||||
I: Integer;
|
||||
Src: ^Pointer;
|
||||
Dst: ^Pointer;
|
||||
begin
|
||||
Src := Self.FData + AIndex * SizeOf(Pointer);
|
||||
Result := Src^;
|
||||
I := AIndex;
|
||||
while I < Self.FCount - 1 do
|
||||
begin
|
||||
Dst := Self.FData + I * SizeOf(Pointer);
|
||||
Src := Self.FData + (I + 1) * SizeOf(Pointer);
|
||||
Dst^ := Src^;
|
||||
I := I + 1
|
||||
end;
|
||||
Self.FCount := Self.FCount - 1
|
||||
end;
|
||||
|
||||
{ ================================================================== }
|
||||
{ TStringList }
|
||||
{ ================================================================== }
|
||||
|
|
@ -470,6 +528,18 @@ begin
|
|||
Self.FCount := Self.FCount + 1
|
||||
end;
|
||||
|
||||
procedure TStringList.AddStrings(ASource: TStringList);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
I := 0;
|
||||
while I < ASource.FCount do
|
||||
begin
|
||||
Self.Add(ASource.Get(I));
|
||||
I := I + 1
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStringList.GetText: string;
|
||||
var
|
||||
I: Integer;
|
||||
|
|
@ -483,7 +553,7 @@ begin
|
|||
begin
|
||||
Ptr := Self.FStrings + I * SizeOf(string);
|
||||
Result := Result + Sep + Ptr^;
|
||||
Sep := #13#10;
|
||||
Sep := #10;
|
||||
I := I + 1
|
||||
end
|
||||
end;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1365
tools/migrate_full.py
Normal file
1365
tools/migrate_full.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue