continued from previous post...
repost of the mainline for reference purposes, functions and procedures already discussed have been removed.
{ --------------------------------------------------------------------------- }
{ MAINLINE ----------------------- }
begin
< snip >
EmitApiName(); { done in previous post }
EmitOpenParenthesis();
DetermineWidestDispositionString();
EmitDispositions();
DetermineWidestParameterName();
EmitParameterNames();
EmitParameterTypes();
EmitClosingParenthesis();
EmitFunctionResult();
end.
after emitting the function name, it is necessary to emit the opening parenthesis. The "hardest" part is placing it in the right spot.
const
PARENTHESIS_FUNCTION_X = 12;
PARENTHESIS_PROCEDURE_X = 13;
PARENTHESIS_OPEN = '(';
PARENTHESIS_CLOSE = ')';
PARENTHESIS_CLOSE_SEMICOLON = ');';
procedure EmitOpenParenthesis();
var
OutputPt : TPOINT;
begin
with OutputPt do
begin
Y := LastLine + SEPARATING_LINES_COUNT + API_OPEN_PARENTHESIS_LINE;
case FunctionApi of
TRUE : X := PARENTHESIS_FUNCTION_X;
FALSE: X := PARENTHESIS_PROCEDURE_X;
end;
end;
Caller.MoveCaretIgnoreEOL(OutputPt);
{ now that we have the caret's coordinate, emit the parenthesis }
Caller.InsertTextAtCaret(PARENTHESIS_OPEN, scamEnd);
end;
The X coordinate of the parenthesis is dependent on whether the definition is for a function or a procedure. It is a simple matter of defining the appropriate constants (unfortunately global since PascalScript does not allow local constants in macros.)
The next procedure is one that should have been done _before_ emitting anything but, it takes care of a detail that was originally overlooked. The originally overlooked detail is that the parameter name is a combination of the C parameter name and its disposition. Therefore, in order to keep everything vertically aligned and not waste any whitespace, it is necessary to determine the widest of the combined string (parameter disposition + parameter name) The original macro naively added the widest parameter name + the widest disposition and, while that works, it wastes whitespace which is visually unappealing. DetermineWidestDispositionString() solves that problem.
var
WidestDispositionString : integer;
WidestDispositionPrefix : integer;
BraceOpenX : integer;
BraceCloseX : integer;
{ ------------------------ }
procedure DispositionStringToDispositionPrefix
(
InDispositionString : string;
var OutDispositionPrefix : string
);
{ removes the underscores from the disposition string and uppercases the }
{ first character }
var
i : integer;
First : string;
begin
OutputDebugString('InDispositionString: ' + InDispositionString, FALSE);
{ loop to remove all the underscores in the disposition string }
i := 1;
while i <= length(InDispositionString) do
begin
if InDispositionString[i] = '_' then
begin
{ remove the underscore }
Delete(InDispositionString, i, 1);
continue;
end;
inc(i);
end;
{ now we need to uppercase the first character }
First := Copy(InDispositionString, 1, 1);
First := Uppercase(First);
OutputDebugString('Uppercased First: ' + First, FALSE);
Delete(InDispositionString, 1, 1);
OutDispositionPrefix := First + InDispositionString;
OutputDebugString('OutDispositionPrefix: ' + OutDispositionPrefix, FALSE);
end;
{ ------------------------ }
procedure DetermineWidestDispositionString();
var
i : integer;
PrefixString : string;
begin
WidestDispositionString := 0;
WidestDispositionPrefix := 0;
for i := PARAMETERS_FIRST_INDEX to DefinitionLinesCount - 1 do
begin
with TokenizedLines[i] do
begin
if Length(ParmDisposition) > WidestDispositionString then
begin
WidestDispositionString := Length(ParmDisposition);
end;
DispositionStringToDispositionPrefix(ParmDisposition, PrefixString);
{ update the width of the widest prefix }
if Length(PrefixString) > WidestDispositionPrefix then
begin
WidestDispositionPrefix := Length(PrefixString);
end;
{ save this parameter's name prefix }
ParmDispositionPrefix := PrefixString;
end;
end;
OutputDebugString('WidestDispositionPrefix: ' + IntToStr(WidestDispositionPrefix),
FALSE);
{ once the widest disposition string is known, it is possible to }
{ calculate the X coordinates of the open and close braces }
case FunctionApi of
TRUE : BraceOpenX := PARENTHESIS_FUNCTION_X + 1;
FALSE: BraceOpenX := PARENTHESIS_PROCEDURE_X + 1;
end;
{ add 2 to account for the space after the opening brace and the space }
{ after the disposition string and 1 more to account for the open brace }
{ itself (3 = 2 spaces + open brace) }
BraceCloseX := BraceOpenX + WidestDispositionString + 3;
end;
There are two widest items to determine. the widest disposition string which is the string that is between an open and close brace and the widest parameter name (as already explained above.) The former is very easy to determine. The latter is done in two steps.
The first step is to determine the parameter prefix which is the parameter disposition with the underscores removed and the first character capitalized. That's referred to as the DispositionPrefix.
The widest parameter name is the widest string resulting from concatenating the parameter name with its disposition prefix. To simplify things a bit, DispositionStringToDispositionPrefix() determines what the disposition prefix is for a given parameter. The prefix will later be used to determine the widest parameter name (combination of prefix + C parameter name.) Note that all that's needed to implement these functions are the PascalScript functions Delete, Copy, Uppercase and Length.
Once the widest disposition string has been calculated, the disposition string can be emited. This is what EmitDispositions() does.
const
BRACE_OPEN = '{ ';
BRACE_CLOSE = '}';
procedure EmitDispositions();
var
OutputPt : TPOINT;
i : integer;
Parameter : integer; { 0 based parameter ordinal }
begin
Parameter := 0;
for i := PARAMETERS_FIRST_INDEX to DefinitionLinesCount - 1 do
begin
with OutputPt do
begin
x := BraceOpenX;
y := LastLine + SEPARATING_LINES_COUNT + API_FIRST_PARAMETER_LINE +
Parameter;
end;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(BRACE_OPEN, scamEnd); { open brace }
Caller.InsertTextAtCaret(TokenizedLines[i].ParmDisposition, scamEnd);
OutputPt.X := BraceCloseX;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(BRACE_CLOSE, scamEnd); { close brace }
inc(Parameter);
end;
end;
As is the case with other Emit... functions, all that's needed to implement this function are Caller.MoveCaretIgnoreEOL and Caller.InsertTextAtCaret. It is simple because _what_ needs to be output has already been determined by other functions.
while DetermineWidestDispositionString() did all the ground work that is needed to calculate the widest parameter name, it did not concern itself with the widest parameter name, it concerned itself only with determining the widest disposition string. The determination of the widest parameter name is done by the "surprisingly" named DetermineWidestParameterName() procedure.
var
{ the parameter name width is the width of the parameter name plus the }
{ width of its disposition prefix. the widest of that addition is the }
{ widest of all parameter names }
WidestParameterName : integer;
procedure DetermineWidestParameterName();
var
i : integer;
begin
WidestParameterName := 0;
for i := PARAMETERS_FIRST_INDEX to DefinitionLinesCount - 1 do
begin
with TokenizedLines[i] do
begin
if Length(ParmName) + Length(ParmDispositionPrefix) > WidestParameterName then
begin
WidestParameterName := Length(ParmName) + Length(ParmDispositionPrefix);
end;
end;
end;
end;
As it has already been mentioned, the widest parameter name is the widest of the combination of Disposition prefix + C parameter name. Only the PascalScript's function Length is needed for this procedure.
Once the widest parameter name is known, everything needed to emit the parameter names is known.
var
ColonX : integer;
const
COLON_CHAR = ':'#0;
procedure EmitParameterNames();
var
OutputPt : TPOINT;
i : integer;
ParameterIndex : integer; { 0 based parameter ordinal }
ParameterName : string; { Parameter prefix + C definition name }
begin
ParameterIndex := 0;
ColonX := BraceCloseX + 2 +
WidestParameterName + 1;
OutputDebugString('WidestDispositionPrefix: ' + IntToStr(WidestDispositionPrefix) + ' ' +
'WidestParameterName: ' + IntToStr(WidestParameterName) + ' ' +
'BraceCloseX: ' + IntToStr(BraceCloseX) + ' ' +
'ColonX: ' + IntToStr(ColonX),
FALSE);
for i := PARAMETERS_FIRST_INDEX to DefinitionLinesCount - 1 do
begin
with OutputPt do
begin
x := BraceCloseX + 2;
y := LastLine + SEPARATING_LINES_COUNT + API_FIRST_PARAMETER_LINE +
ParameterIndex;
end;
Caller.MoveCaretIgnoreEOL(OutputPt);
with TokenizedLines[i] do
begin
ParameterName := ParmDispositionPrefix + ParmName;
end;
Caller.InsertTextAtCaret(ParameterName, scamEnd);
OutputPt.X := ColonX;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(COLON_CHAR, scamEnd);
inc(ParameterIndex);
end;
end;
As with other Emit... functions, this function is simply calls to Caller.MoveCaretIgnoreEOL and Caller.InsertTextAtCaret.
After the parameter names have been emitted, it is time to emit the parameter types.
EmitParameterTypes code:
const
{ PTT = Parameter Types Table }
PTT_LO = 1;
PTT_HI = 1;
type
TPARAMETERS_TYPES_TABLE = array[PTT_LO..PTT_HI]
of record
ptt_c_type : string;
ptt_pascal_type : string;
end;
var
ParameterTypesTable : TPARAMETERS_TYPES_TABLE;
procedure TranslateParameterType
(
InCParameterType : string;
var OutPascalParameterType : string
);
begin
{ initialize }
InCParameterType := Uppercase(InCParameterType);
OutPascalParameterType := '';
{ look for a match }
{ having the "exit" in a separate condition makes it easier to copy/paste }
{ to additional type translations }
if InCParameterType = 'HANDLE' then OutPascalParameterType := 'THANDLE';
if OutPascalParameterType <> '' then exit;
if InCParameterType = 'PCSTR' then OutPascalParameterType := 'pchar';
if OutPascalParameterType <> '' then exit;
if InCParameterType = 'PSTR' then OutPascalParameterType := 'pchar';
if OutPascalParameterType <> '' then exit;
if InCParameterType = 'PVOID' then OutPascalParameterType := 'pointer';
if OutPascalParameterType <> '' then exit;
{ add additional type translations here }
{ etc... }
{ no match, make the Pascal parameter type equal the C parameter type }
OutPascalParameterType := InCParameterType;
end;
{ ------------------------ }
var
{ this could have been a constant... oh well }
Semicolon : string;
procedure EmitParameterTypes();
var
OutputPt : TPOINT;
ParameterIndex : integer;
i : integer;
PascalParameterType : string;
begin
PascalParameterType := '**unset**'; { to make any problems obvious }
Semicolon := ';';
for i := PARAMETERS_FIRST_INDEX to DefinitionLinesCount - 1 do
begin
with OutputPt do
begin
x := ColonX + 2;
y := LastLine + SEPARATING_LINES_COUNT + API_FIRST_PARAMETER_LINE +
ParameterIndex;
end;
Caller.MoveCaretIgnoreEOL(OutputPt);
with TokenizedLines[i] do
begin
TranslateParameterType(ParmType, PascalParameterType);
Caller.InsertTextAtCaret(PascalParameterType, scamEnd);
if i < DefinitionLinesCount - 1 then
begin
Caller.InsertTextAtCaret(Semicolon, scamEnd);
end;
inc(ParameterIndex);
end;
end;
end;
about the only thing that is notable about EmitParameterTypes() is that it translates C types into Pascal types, e.g, PVOID into pointer, HANDLE into THANDLE, etc.
The rest is more of the same, Caller.MoveCaretIgnoreEOL and Caller.InsertTextAtCaret.
Now that the parameters disposition, type and name have been emitted, all that's left is to emit the closing parenthesis and the function result (if any)
EmitClosingParenthesis() routine:
procedure EmitClosingParenthesis();
var
OutputPt : TPOINT;
begin
OutputPt.Y := LastLine + SEPARATING_LINES_COUNT + API_FIRST_PARAMETER_LINE +
(DefinitionLinesCount - PARAMETERS_FIRST_INDEX);
case FunctionApi of
TRUE :
begin
OutputPt.X := PARENTHESIS_FUNCTION_X;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(PARENTHESIS_CLOSE, scamEnd);
end;
FALSE:
begin
OutputPt.X := PARENTHESIS_PROCEDURE_X;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(PARENTHESIS_CLOSE_SEMICOLON, scamEnd);
end;
end;
end;
Just look at the code, it's just more of the same.
EmitFunctionResult() routine:
const
SPACE_CHAR = ' ';
SEMICOLON_CHAR = ';';
const
EXPORT_DATA = ' stdcall; external ';
{ the following needs to be customized or prompted for }
EXPORT_DLL = 'ImageHlp;';
procedure EmitFunctionResult();
var
OutputPt : TPOINT;
PascalReturnType : string;
CReturnType : string;
begin
{ if it's a procedure, there is no result to emit }
if not FunctionApi then exit;
with OutputPt do
begin
x := length(FUNCTION_TOKEN) + 1;
y := LastLine + SEPARATING_LINES_COUNT + API_FIRST_PARAMETER_LINE +
(DefinitionLinesCount - PARAMETERS_FIRST_INDEX) + 1;
end;
Caller.MoveCaretIgnoreEOL(OutputPt);
Caller.InsertTextAtCaret(COLON_CHAR, scamEnd);
OutputPt.X := OutputPt.X + 2;
Caller.MoveCaretIgnoreEOL(OutputPt);
CReturnType := TokenizedLines[1].FunctionResultType;
TranslateParameterType(CReturnType, PascalReturnType);
Caller.InsertTextAtCaret(PascalReturnType + SEMICOLON_CHAR, scamEnd);
Caller.InsertTextAtCaret(EXPORT_DATA, scamEnd);
Caller.InsertTextAtCaret(EXPORT_DLL, scamEnd);
end;
You may not know the drill but, by now, you should know the code.
The attachment has the macro and the example program the macro acts on. The majority of calls to OutputDebugString are suppressed but, a few calls have been left active to emphasize the most important points/parts.
Also, very educational, once the macro has fully played do repeated undo(s). That will reverse-play the macro and show how the undo facility creates its undo blocks depending on the macro code. Not only its educational, it's fun to watch. Undo like an Egyptian.

What is truly remarkable is that it took roughly less than a dozen PascalScript and SynEdit functions to implement a macro that does a somewhat elaborate conversion from a C API definition to a Pascal definition.
finally... there is no more code to post.
Hopefully, these examples will be educational and useful to some.