How can I make signaling NaNs easy to work with? - delphi

The IEEE754 standard defines two classes of NaN, the quiet NaN, QNaN, and the signaling NaN, SNaN. When an SNaN is loaded into a floating point register, an exception is raised by the floating point unit.
QNaN is available to Delphi code through the constant named NaN that is declared in Math. The definition of that constant is:
const
NaN = 0.0 / 0.0;
I would like to be able to use something similar to declare a constant that is a signaling NaN, but have not yet found a way to do that.
Naively you might write this code:
function SNaN: Double;
begin
PInt64(#Result)^ := $7FF7FFFFFFFFFFFF;//this bit pattern specifies an SNaN
end;
But the ABI for floating point return values means that the SNaN is loaded into a floating point register so that it can be returned. Naturally that leads to an exception which rather defeats the purpose.
So you are then led to writing code like this:
procedure SetToSNaN(out D: Double);
begin
PInt64(#D)^ := $7FF7FFFFFFFFFFFF;
end;
Now, this works, but it's very inconvenient. Suppose you need to pass an SNaN to another function. Ideally you would like to write:
Foo(SNaN)
but instead you have to do this:
var
SNaN: Double;
....
SetToSNaN(SNaN);
Foo(SNaN);
So, after the build-up, here's the question.
Is there any way to write x := SNaN and have the floating point variable x assigned a value that is a signaling NaN?

This declaration solves it at compile time:
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double absolute iNaN;
The compiler still treats the SNaN as a constant.
Trying to assign a value to SNaN will give a compile time error: E2064 Left side cannot be assigned to.
procedure DoSomething( var d : Double);
begin
d := 2.0;
end;
SNaN := 2.0; // <-- E2064 Left side cannot be assigned to
DoSomething( SNaN); // <--E2197 Constant object cannot be passed as var parameter
WriteLn(Math.IsNaN(SNaN)); // <-- Writes "true"
Should you have the compiler directive $WRITEABLECONSTS ON (or $J+), this could be turned off temporarily to ensure not altering SNaN.
{$IFOPT J+}
{$DEFINE UNDEFWRITEABLECONSTANTS}
{$J-}
{$ENDIF}
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double ABSOLUTE iNaN;
{$IFDEF UNDEFWRITEABLECONSTANTS}
{$J+}
{$ENDIF}

Here's another workaround:
type
TFakeRecord = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TFakeRecord = ( i: $7FF7FFFFFFFFFFFF);
The debugger shows IEEE754.SNaN as +NAN, however when you access it you'll still get a floating point exception. A workaround for that could be:
type
ISet8087CW = interface
end;
TISet8087CW = class(TInterfacedObject, ISet8087CW)
protected
OldCW: Word;
public
constructor Create(const NewCW: Word);
destructor Destroy; override;
end;
TIEEE754 = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TIEEE754 = ( i: $7FF7FFFFFFFFFFFF);
{ TISet8087CW }
constructor TISet8087CW.Create(const NewCW: Word);
begin
OldCW := Get8087CW;
Set8087CW(NewCW);
inherited Create;
end;
destructor TISet8087CW.Destroy;
begin
Set8087CW(OldCW);
inherited;
end;
procedure TForm6.Button4Click(Sender: TObject);
var
CW: ISet8087CW;
begin
CW := TISet8087CW.Create($133F);
Memo1.Lines.Add(Format('SNaN: %f', [IEEE754.SNaN]));
end;

You can inline the function:
function SNaN: Double; inline;
begin
PInt64(#Result)^ := $7FF7FFFFFFFFFFFF;
end;
But it will depend on the optimization and compiler mood.
I've seen some functions not inlined, without any clear understanding from the context. I do not like either relying on inlining.
What I would better do, and which will work on all versions of Delphi, is to use a global variable:
var
SNaN: double;
Then set it in the initialization block of the unit:
const
SNaN64 = $7FF7FFFFFFFFFFFF;
initialization
PInt64(#SNaN)^ := SNaN64;
end.
Then you will be able to use SNaN just as a regular constant. That is, you can write code as expected:
var test: double;
...
test := SNaN;
In the IDE debugger, it will be shown as "test = +NAN", which is the expected result, I suppose.
Note that using this SNaN will raise an exception when it is read into the FPU stack (e.g. if test=0 then) so you have to check the value at binary level... this is the reason why I defined a SNaN64 constant, which will make very fast code by the way.
toto := SNaN;
if PInt64(#toto)^<>SNaN64 then // will run and work as expected
DoubleToString(toto);
if toto<>SNaN then // will raise an EInvalidOp at runtime
DoubleToString(toto);
You can change this behavior by changing the x87 exception register:
backup := Set8087CW($133F);
try
..
finally
Set8087CW(backup);
end;
I suppose this to be set globally for your program, in all extend of the code which will have to handle this SNaN constant.

I use a function:
Function UndefinedFloat : double
Begin
Result := Nan
End;
This then works
Var
MyFloat : double;
Begin
MyFloat := UndefinedFloat;

Here's a rather dirty way to do it, that results in very clean code for the consumer.
unit uSNaN;
interface
const
SNaN: Double=0.0;//SNaN value assigned during initialization
implementation
initialization
PInt64(#SNaN)^ := $7FF7FFFFFFFFFFFF;
end.
I was expecting the linker to put SNaN in a read-only segment of the executable but it appears not to do so. In any case, even if it did you could use VirtualProtect to get around that for the duration of the assignment.

Related

What is the purpose of the Count parameter of TStream.WriteData and TStream.ReadData?

The TStream class contains many overloads of WriteData that are of this form:
function WriteData(const Buffer: Int32; Count: Longint): Longint; overload;
There are overloads for all the usual suspects, AnsiChar, Char, UInt32, Double and so on. Similarly for ReadData. I'm trying to understand what purpose the Count parameter serves. The implementation of the overload mentioned above is as follows:
function TStream.Skip(Amount: Integer): Integer;
var
P: Integer;
begin
P := Position;
Result := Seek(Amount, soCurrent) - P;
end;
function TStream.WriteData(const Buffer: Int32; Count: Longint): Longint;
const
BufSize = SizeOf(Buffer);
begin
if Count > BufSize then
Result := Write(Buffer, BufSize) + Skip(Count - BufSize)
else
Result := Write(Buffer, Count)
end;
I can obviously see what this code does, but I cannot understand why it would make sense to perform a partial write. Why would it ever make sense to call this function with Count < BufSize? The behaviour then is very odd.
Does anyone know why these overloads were added and what purpose they are intended for? Naturally I've looked at the documentation which has nothing to say about these methods.
As an aside I will submit bug report concerning this line:
Result := Write(Buffer, BufSize) + Skip(Count - BufSize);
It is a mistake to assume that the call to Write will occur before the call to Skip. The evaluation order of the operands to the + operator is not defined. This code should rather be written like this:
Result := Write(Buffer, BufSize);
inc(Result, Skip(Count - BufSize));
Theory crafting
if TStream predate the introduction of the overload keyword (Delphi 3 IIRC), they probably introduced a single method to write integer that was probably int32. When calling the function with a "byte" variable, it would get passed to the function as Integer, and then the Count parameter would only allow to write a single byte. Now they support this for backward compatibility purpose.
In some cases(like next one), supporting Count < Bufsize is indeed especially silly :
function WriteData(const Buffer: Int8; Count: Longint): Longint; overload;
Another justification would be in the next situation when a variable only need to be saved to stream as an Int8 but is worked on as a Int32 during program execution (because it is passed to a function that only takes a var : Int32 as parameter).
procedure SomeProc(var MyInt : Integer);
procedure DoSomeStream;
var
iVal : Integer;
// bVal : ShortInt;
begin
SomeProc(iVal);
Stream.WriteData(iVal, SizeOf(Byte));
//Instead of
// SomeProc(iVal);
// bVal := iVal;
// Stream.WriteData(bVal)
end;
I'm not saying it's required (can be worked around) but in some corner case situation, it could be useful.
For me it seems that this code enables you to write some data and than skip to a position far behind the data.
e.g. you have a stream containing multiple integers and you want to overwrite every 5th, you can do it with:
mData := 15;
WriteData(mData, SizeOf(mData) * 5);

E2009 Incompatible types: 'Parameter lists differ'

I get the following error:
E2009 Incompatible types: 'Parameter lists differ'
However I disagree, looking at the definitions I can see no difference.
Here's the record definition:
type
TFastDiv = record
private
...
DivideFunction: function (const Buffer: TFastDiv; x: integer): integer;
And here's the Mod function I want to assign:
function dividefixedi32(const Buffer: TFastDiv; x: integer): integer;
asm
The following assignment issues the error:
class operator TFastDiv.Implicit(a: integer): TFastDiv;
begin
if (a = 0) then begin
raise EDivByZero.Create('Setting a zero divider is a division by zero error')
at ReturnAddress;
end;
Result.FSign:= Math.sign(a);
case Result.FSign of
-1: begin
SetDivisorI32(Result, a);
Result.DivideFunction:= dividefixedi32; <<-- error E2009
What's wrong with my code?
SSCCE
unit SSCCE;
interface
uses Math;
type
TFastDiv = record
private
FBuffer: UInt64; // The reciprocal of the divider
FDivider: integer; // The divider itself (need with modulus etc).
FSign: TValueSign;
DivideFunction: function (const Buffer: TFastDiv; x: integer): integer;
ModFunction: function (const Buffer: TFastDiv; x: integer): integer;
public
class operator Implicit(a: integer): TFastDiv;
end;
implementation
uses SysUtils;
function dividefixedi32(const Buffer: TFastDiv; x: integer): integer; forward;
class operator TFastDiv.Implicit(a: integer): TFastDiv;
begin
if (a = 0) then begin raise EDivByZero.Create('Setting a zero divider is a division by zero error') at ReturnAddress; end;
Result.FSign:= Math.sign(a);
case Result.FSign of
-1: begin
//SetDivisorI32(Result, a);
Result.DivideFunction:= dividefixedi32;
end; {-1:}
1: begin
//SetDivisorU32(Result.FBuffer, a);
end; {1:}
end; {case}
Result.FDivider:= a;
end;
function dividefixedi32(const Buffer: TFastDiv; x: integer): integer;
asm
mov eax, edx
mov r8d, edx // x
mov r9, rcx // Buffer
imul dword [r9] // m
lea eax, [rdx+r8] // r8 = r8 or rsi
mov ecx, [r9+4] // shift count
sar eax, cl
sar r8d, 31 // sign(x)
sub eax, r8d
ret
end;
end.
First of all, some general advice. Your SSCCE is poor. It is neither short nor self-contained. This is actually rather important. Making the demonstration code as short as possible frequently helps you understand the problem. That is definitely the case here.
Here's my take on an SSCCE:
program soq19147523_version1;
type
TRecord = record
data: Integer;
proc: procedure(const rec: TRecord);
end;
procedure myproc(const rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // fail, E2009
end;
begin
end.
This fails to compile with E2009. You can make it compile a number of ways. For instance, removing the data member results in successful compilation.
program soq19147523_version2;
type
TRecord = record
proc: procedure(const rec: TRecord);
end;
procedure myproc(const rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // compiles
end;
begin
end.
In XE3 you can make it compile by adding the [ref] attribute to the parameter of the procedural type. To be explicit, this compiles in XE3:
program soq19147523_version3;
type
TRecord = record
data: Integer;
proc: procedure(const [ref] rec: TRecord);
end;
procedure myproc(const [ref] rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // compiles in XE3, no [ref] in XE2
end;
begin
end.
This gives us a strong clue as to what the compiler is doing. An undecorated const record parameter is passed either by value or by reference. If the record is small enough to fit into a register, it will be passed by value.
When the compiler is processing the record, it has not fully finalised the record's size. I'm guessing that internally in the compiler there is a variable containing the size of the record. Until the record's declaration is complete, I posit that this size variable is zero. So the compiler decides that the const parameter of record type will be passed by value in a register. When the procedure myproc is encountered, the record's true size is known. It does not fit in a register, and the compiler therefore recognises a mismatch. The type in the record receives its parameter by value, but that being offered for assignment passes the parameter by reference.
Indeed, you can remove the [ref] from the declaration of myproc and the program still compiles.
This also explains why you found that using a var parameter resulted in successful compilation. This obviously forces the parameter to be passed by reference.
If you can move to XE3 or later then the solution is obvious: use [ref] to force the compiler's hand.
If you cannot move to XE3 then perhaps an untyped const parameter is the best solution. This also forces the compiler to pass the parameter by reference.
program soq19147523_version4;
type
TRecord = record
data: Integer;
proc: procedure(const rec{: TRecord});
end;
procedure myproc(const rec{: TRecord});
begin
Writeln(TRecord(rec).data);
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc;
end;
begin
end.
Regular readers of my postings here on Stack Overflow will know that I'm a big proponent of operator overloading on value type records. I use this feature extensively and it results in efficient and highly readable code. However, when you start pushing hard with more complex and interdependent types, the design and implementation breaks down.
The flaw highlighted in this question is one good example. It's really not uncommon to expect that the compiler could handle this. It's very reasonable to expect a type to be able to refer to itself.
Another example where the implementation lets the programmer down is when you wish to put a const of the record type in that record. For example, consider this type:
type
TComplex = record
public
R, I: Double;
const
Zero: TComplex = (R: 0.0, I: 0.0);
end;
This fails to compile at the declaration of Zero with E2086 Type 'TComplex' is not yet completely defined.
Another limitation is the inability of type A to refer to type B, and vice versa. We can make forward declarations for classes, but not records. I understand that the compiler implementation would need to be modified to support this, but it's certainly possible to achieve.
And there's more. Why is it not possible to allow inheritance for records? I don't want polymorphism, I just want to inherit the data members and methods of the record. And I don't even need the is a behaviour that you get with classes. That is I don't mind if TDerivedRecord is not a TBaseRecord. All I want is to inherit members and functions to avoid duplication.
Sadly, to my mind, this is a feature that has been done 90% and is just missing the tender, loving care needed to take it to completion.
Workaround
If I change the code like so:
record definition
type
TFastDiv = record
private
...
DivideFunction: function (var Buffer: TFastDiv; x: cardinal): cardinal;
^^^
function definition
function dividefixedu32(var Buffer: TFastDiv; x: Cardinal): cardinal; // unsigned
asm // ^^^
The problem goes away as well.
Note that if I change the var back into const the problem recurs.

What difference does it make when I use "const" in a procedure's parameter?

What difference does it make when I use a const parameter in a procedure?
Take the following procedure for example:
procedure DoSomething(Sender: TObject; const Text: String; var Reply: String);
begin
//Text is read-only and Reply will be passed back wherever DoSomething() was called
Reply:= Text;
end;
The parameter Text: String is prefixed with const so that (as far as I know), a copy of the value is made and used - and is read-only. What I was wondering is how is does this affect the application any differently than if I didn't put const there? Perhaps a performance trick?
Looking at the documentation states:
"Using const allows the compiler to optimize code for structured - and string-type parameters. It also provides a safeguard against unintentionally passing a parameter by reference to another routine."
In case of a string for example the optimization means there is no additional refcounting when passing as const. Also passing as const does not mean it's a copy. Often it internally passes as reference because the compiler ensures no write access to it.
Some very interesting articles to completly understand what's going on under the hood:
http://delphitools.info/2010/07/28/all-hail-the-const-parameters
http://vcldeveloper.com/articles/different-function-parameter-modifiers-in-delphi
Edit:
A simple example to show that const may result in pass by reference internally:
program Project1;
{$APPTYPE CONSOLE}
type
PMyRecord = ^TMyRecord;
TMyRecord = record
Value1: Cardinal;
Value2: Cardinal;
end;
procedure PassAsConst(const r: TMyRecord);
begin
PMyRecord(#r).Value1 := 3333;
PMyRecord(#r).Value2 := 4444;
end;
procedure PassByVal(r: TMyRecord);
begin
PMyRecord(#r).Value1 := 3333;
PMyRecord(#r).Value2 := 4444;
end;
var
r: TMyRecord;
begin
r.Value1 := 1111;
r.Value2 := 2222;
PassByVal(r);
Writeln(r.Value1);
Writeln(r.Value2);
PassAsConst(r);
Writeln(r.Value1);
Writeln(r.Value2);
Readln;
end.
When you don't have the const prefix, the compiler has to assume that you will be changing the parameter. That means copying it and setting up a hidden try...finally to dispose of the local string variable, so sometimes the const can yield a significant performance improvement. It also makes the generated code smaller.
In addition to the previous answers of efficiency when using a const (i.e. the compiler does not need to copy the variable), if you use a const with an Interface parameter, it prevents the triggering of ref counting.
When you use const string parameter in your function it is your promise to Delphi compiler that you would not call any other function from it, at least not before you made copies of all those const string parameters into local variables you would have for them.
https://github.com/the-Arioch/XE2_AutoOpenUnit/blob/master/Delphi_String_Bug/Girli_str_2xFree_Minimized.dpr
https://plus.google.com/+AriochThe/posts/WB3toSpAdfA
program Girli_str_2xFree_Minimized;
{$APPTYPE CONSOLE}
// initial mini-demo by Ãèðëèîíàéëüäî - http://www.sql.ru/forum/memberinfo.aspx?mid=249076
// variance IfDef's re-added by Arioch (orginal reporter)
uses
  SysUtils;
{.$Define TestBug_Impl_Exit}
{.$Define TestBug_Impl_AsIs}
{.$Define NewStr_Unique}
var
  rFile: string;
// global or local does not matter.
// originally it was an object member.
{$IfNDef TestBug_Impl_Exit} {$IfNDef TestBug_Impl_AsIs}
function TestBUG(const S: string): string;
begin
  Result := S;
end;
{$EndIf}{$EndIf}
{$IfDef TestBug_Impl_AsIs}
procedure TestBUG(const S: string; var Result: string);
begin
  Result := S;
end;
{$EndIf}
{$IfDef TestBug_Impl_Exit}
function TestBUG(const S: string): string;
begin
  Exit(S);
end;
{$EndIf}
procedure Test(const FileName: string);
{$IfDef TestBug_Impl_AsIs} var unnamed_temp: string; {$EndIf}
begin
// rFile := FileName.SubString(0, Length(FileName)); // unavail in XE2
{$IfNDef NewStr_Unique}
  rFile := Copy(FileName, 1, Length(FileName));
  // reference-counting broken, de facto writes into const-string (destroys it)
{$Else}
  rFile := FileName; // no bug, reference-counting proceeded normally!
  UniqueString(rFile);
{$EndIf}
{$IfNDef TestBug_Impl_AsIs}
  TestBUG(FileName); // try to use the const-pointer to the old string
{$Else}
  TestBUG(FileName, unnamed_temp);
{$EndIf}
end; // <== Fatality here
begin
  try
    try
      rFile := ParamStr(0);
      Test(rFile);
      Writeln('Safely returned from the hazardous function without memory dislocations.');
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    Writeln;
    Writeln('Read the output. Press ENTER to terminate the program.');
    Readln;
  end;
end.
Since volatile string and const string are de facto two different types - the classic compiler treats them differently - there should had been data conversion added: when calling const-string function passing it volatile-string parameters Delphi could had increment the use counter and decrease it after function exit. Passing const-strings parameters down the line into next const-string function would be just how it is now, once compiler typecasted volatile string into const string it can remain so.
Sadly, it does not. And here be dragons.
So, if your function has const string parameters - either do not call from it or cache those parameters into local vars.

How to obtain the line numbers of executable lines from DWScript context map or symbol table

I am writing an IDE to use with Delphi DWScript and now have a simple debuggable script. I now want to highlight the executable lines in my source (like the blue dots at the left of the Delphi source). Digging for examples / information I see that there is a program 'SymbolDictionary' where I can call 'FindSymbolUsage( suReference)' - this seems to give me positions of symbols 'being referred to', and I guess I could call this again with 'suImplementation' to get the lines where there is an assignment. This has made me realise though that I could do with understanding what the structure and purpose of the ContextMap and the SymbolDictionary actually are. Does anyone have an example of listing the executable line numbers of the script?
My fledgling code is reproduced below and is awaiting critical analysis :-)
Thanks
TExecutableLines = class( TObject )
constructor Create;
destructor Destroy; override;
PRIVATE
FLines : TBits;
function GetIsExecutable(ALineNumber: integer): boolean;
procedure SetIsExecutable(ALineNumber: integer; const Value: boolean);
PUBLIC
procedure Clear;
procedure Evaluate( AProgram : IdwsProgram; const AUnitName : string );
property IsExecutable[ALineNumber : integer] : boolean
read GetIsExecutable
write SetIsExecutable;
end;
{ TExecutableLines }
procedure TExecutableLines.Clear;
begin
FLines.Size := 0;
FLines.Size := 1024;
end;
constructor TExecutableLines.Create;
begin
inherited;
FLines := TBits.Create;
end;
destructor TExecutableLines.Destroy;
begin
FreeAndnil( FLines );
inherited;
end;
procedure TExecutableLines.Evaluate(AProgram: IdwsProgram; const AUnitName : string);
var
I : integer;
Pos : TSymbolPosition;
begin
Clear;
For I := 0 to AProgram.SymbolDictionary.Count-1 do
begin
Pos := AProgram.SymbolDictionary.FindSymbolPosList(
AProgram.SymbolDictionary[I].Symbol ).FindUsage( suReference);
if Pos <> nil then
If Pos.ScriptPos.IsMainModule then
IsExecutable[ Pos.ScriptPos.Line ] := True
else
if SameText( Pos.UnitName, AUnitName ) then
IsExecutable[ Pos.ScriptPos.Line ] := True
end;
end;
function TExecutableLines.GetIsExecutable(ALineNumber: integer): boolean;
begin
if ALineNumber = 0 then
raise Exception.Create('Lines numbers are 1..n' );
if ALineNumber < FLines.Size then
Result := FLines[ALineNumber]
else
Result := False;
end;
procedure TExecutableLines.SetIsExecutable(ALineNumber: integer;
const Value: boolean);
begin
if ALineNumber = 0 then
raise Exception.Create('Lines numbers are 1..n' );
if ALineNumber >= FLines.Size then
FLines.Size := ALineNumber+1;
FLines[ALineNumber] := Value;
end;
The TdwsSymbolDictionary serves a different purpose, mostly knowing where (if) a symbol is declared or used, as well as easing up things like rename refactoring (see http://delphitools.info/2011/02/19/spotlight-on-tsymboldictionary/).
The TdwsSourceContextMap serves to know where in the source code, code "blocks" are (f.i. where a class declaration starts and ends, where function implementation starts and ends, etc.), it's both useful to "jump" to a position in code, or to know where the cursor is in terms of symbols.
What you're after is yet another info, it's what lines correspond to compiled expressions. For that you need to look at what is compiled, TExprBase.SubExpr/SubExprCount are your workhorses, or the utility function that wraps them RecursiveEnumerateSubExprs. With that function you can look at all the expressions in your program, start from TdwsProgram.Expr and TdwsProgram.InitExpr (you can cast the IdwsProgram to a TdwsProgram to get at those properties).
These are where you can have breakpoints.
As an illustration, suppose you have
1. function Dummy : Integer;
2. var i : Integer;
3. begin
4. while i<10 do begin
5. Inc(i);
6. if i>5 then
7. break;
8. end;
9. end;
Then if I'm not mistaken (doing this out of the blue):
The symbol dictionary will list a declaration of "Dummy" at 1, a
usage of "Integer" at 1 & 2, a declaration of "i" at 2, a usage of "i" at
4, 5 & 6.
The context map will have a block for the function, the main block
and the while loop.
The lines with compiled expressions will be 2 (.InitExpr) and 4, 5,
6 & 7 (.Expr)

Loosen "Local procedure/function assigned to procedure variable" restriction gracefully

Consider the following test-case:
{ CompilerVersion = 21 }
procedure Global();
procedure Local();
begin
end;
type
TProcedure = procedure ();
var
Proc: TProcedure;
begin
Proc := Local; { E2094 Local procedure/function 'Local' assigned to procedure variable }
end;
At line 13 compiler emits message with ERROR level, prohibiting all of the cases of such local procedures usage. "Official" resolution is to promote Local symbol to the outer scope (ie: make it a sibling of Global) which would have negative impact on code "structuredness".
I'm seeking the way to circumvent it in most graceful manner, preferably causing compiler to emit WARNING level message.
Your best bet is to declare it as reference to procedure using the new anonymous methods feature and then you can keep everything nicely encapsulated.
type
TProc = reference to procedure;
procedure Outer;
var
Local: TProc;
begin
Local := procedure
begin
DoStuff;
end;
Local;
end;
This gets around the issues that Mason describes by capturing any variables local to the anonymous function.
Here's why you can't do it:
type
TProcedure = procedure ();
function Global(): TProcedure;
var
localint: integer;
procedure Local();
begin
localint := localint + 5;
end;
begin
result := Local;
end;
Local procedures have access to the outer routine's variable scope. Those variables are declared on the stack, though, and become invalid once the outer procedure returns.
However, if you're using CompilerVersion 21 (Delphi 2010), you've got anonymous methods available, which should be able to do what you're looking for; you just need a slightly different syntax.
If one really needs to use local procedures in D7 or earlier one could use this trick:
procedure GlobalProc;
var t,maxx:integer; itr,flag1,flag2:boolean; iterat10n:pointer;
//Local procs:
procedure iterat10n_01;begin {code #1 here} end;
procedure iterat10n_10;begin {code #2 here} end;
procedure iterat10n_11;begin {code #1+#2 here} end;
begin
//...
t:=ord(flag2)*$10 or ord(flag1);
if t=$11 then iterat10n:=#iterat10n_11
else if t=$10 then iterat10n:=#iterat10n_10
else if t=$01 then iterat10n:=#iterat10n_01
else iterat10n:=nil;
itr:=(iterat10n<>nil);
//...
for t:=1 to maxx do begin
//...
if(itr)then asm
push ebp;
call iterat10n;
pop ecx;
end;
//...
end;
//...
end;
However the problem is that adress-registers could differ on different machines - so it's needed to write some code using local proc call and look via breakpoint which registers are used there...
And yeah - in most real production cases this trick is just some kind of palliative.
For the records, my homebrewn closure:
{ this type looks "leaked" }
type TFunction = function (): Integer;
function MyFunction(): TFunction;
{$J+ move it outside the stack segment!}
const Answer: Integer = 42;
function Local(): Integer;
begin
Result := Answer;
{ just some side effect }
Answer := Answer + Answer div 2;
end;
begin
Result := #Local;
end;
procedure TForm1.FormClick(Sender: TObject);
var
Func: TFunction;
N: Integer;
begin
{ unfolded for clarity }
Func := MyFunction();
N := Func();
ShowMessageFmt('Answer: %d', [N]);
end;

Resources