Basics of writing SynEdit macros lazarus / 9-Editor ---------------------------------------------------------------------- 440bx · 2025-06-08 15:49 Introduction Macros have the potential to save a lot work.  In addition to that, they can prevent "auto-pilot" mistakes common in repetitive tasks. A SynEdit macro may be a macro that consists of keystrokes that have been recorded.  No programming is needed in this case, simply tell the IDE to start recording, type the text the macro needs to contain, stop recording when done and, simply replay the macro whenever needed.  This thread will not address these macros as they are considered trivial and need no explanation. A SynEdit macro may also be a program that consists of SynEdit key commands logically controlled by a subset of PascalScript.  This allows for creating much more sophisticate macros, giving the editor power to carry out genuinely sophisticated tasks. It is very important to keep in mind that programming a macro is slightly different than traditional programming.  This is mostly due to the fact that it is the editor that is being programmed and, the editor is not a file, a console or a window.  For instance, the editor can be thought of as a grid of n varying length lines and, if the caret is placed at a location (X, Y) and a character is typed there, the editor automatically fills any empty space with spaces that may have existed from the last character before X on line Y to the new character that has been placed in position (X, Y). Basically, when programming the editor, you can place the caret just about anywhere, type something and,  any space that used to be empty between the last existing character and the character ypu typed is automatically filled it with spaces.    This is very convenient and can greatly simplify macro writing. continued on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 16:13 PascalScript limitations in macros PascalScript is a subset of Pascal and when used in a SynEdit macro, there are limitations that must be kept in mind at all times in order to create a working script. 1.  neither local type declarations nor local constants are allowed in macro functions and/or procedures            2.  set declarations cannot use the .. (range) specifier.  This often makes set declarations impractical. e.g, ['a'..'z'], declaring such a set requires specifying every character, i.e, 'a', 'b', 'c', 'd', etc, all the way to 'z'                                                      3.  range types are not allowed, e.g.                                      type                                                                        TRANGE = 0..5;                                                    4.  typed constants are not allowed                                      5.  there is no writeln function in macros                              6.  if the colon is missing in an assignment token, i.e, "=" instead of ":=", PascalScript emits an "Internal error (20)" which isn't of much help to notice that the colon is missing.                             } 7.  break and continue don't seem to work as expected when used in _nested_ loops (strange/random internal error messages are output.)  They do seem to work as expected in non-nested loops.                        8.  there is no debugger, there is no unit facility, there is no include file facility, there is no preprocessor facility.  These restrictions affect how the Pascal source is arranged/structured.                  PascalScript characteristics to always keep in mind: 1. strings in macros are always 1 based      STRONGLY RECOMMENDED:                                                  Because of all the language restrictions and the lack of a capable debugging facility it is advisable to keep the Pascal statements very simple. Typical statements found in a FP program are often too complex for the macro subset of PascalScript.  The more complex the statement is, the harder it becomes to determine which part of the statement is not acceptable to PascalScript.  Statement complexity can also make debugging more difficult than necessary. As the saying goes: keep it simple, it will reward you handsomely. environment characteristics to _always_ keep in mind: 1. the IDE editor internally holds the lines in an array of Lines that is ZERO based.  That is, the line that is shown as line 1 in the source editor has index 0 in that array (the Lines array.) 2. the Caret coordinate, UNLIKE the lines array, is ONE (1) based.  It is extremely easy to forget this and use a caret's Y coordinate as a line index or vice-versa causing an "off by 1" error. 3. when editing a macro, the message window does _not_ clear old messages when the macro is saved (unlike when compiling, every time a program is compiled, the messages window is cleared.)  For this reason, it is convenient to "manually" clear the messages by right- clicking in the messages window and selecting "clear".  Failure to do this can lead the programmer to believe there are still errors that are not there (they simply are old error messages.) this is also a reason to keep the Editor Macro window opened because if there is a problem with the macro a red exclamation sign will appear next to it.  It is still a good idea to clear old messages because the red exclamation mark is often insufficient to rectify the problem (the error message is usually a better source of information.) continued on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 16:26 The functions available when creating a macro are split between functions provided by SynEdit and functions provided by PascalScript. PascalScript functions Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   { IMPORTANT:                                                                }{                                                                           }{ in addition to the list of functions below, there are other absolutely      }{ essential functions listed at:                                            } { https://wiki.freepascal.org/Editor_Macros_PascalScript                    } { the information contained in the above page is a MUST read                }   { CREDIT: list obtained from https://www.elrasoft.com/uuprogs_help/HTML/standardlibrary_id.htm } { sorted on function/procedure name                                         }{ parameters, type and result type, column aligned                          }{ all type identifiers lowercased                                           }{ all definition semicolon ended                                            }  function  Abs            (    e : extended)                                                         : extended; function  Copy           (    s : string;     ifrom, icount : longint)                              : string;function  Cos          (    e : extended)                                                       : extended; procedure Delete         (var s                           : string;      ifrom, icount : longint) : string; function  FloatToStr     (    e : extended)                 : string;function  FloatToStr     (    e : extended)                 : string; function  GetArraylength (var v                             : array)                              : Integer; procedure Insert         (    s : string; var s2            : string;      ipos          : longint) : string;function  Int            (    e : extended)                                                       : longint;function  IntToStr     (    i : longint)                                                        : string; function  Length         (    s : string)                                                         : longint;function  Lowercase      (    s : string)                                                         : string; function  Padl           (    s : string;     I             : longint)                              : string;function  Padr           (    s : string;     I             : longint)                              : string;function  Padz         (    s : string;     I             : longint)                            : string;function  Pi                                                                                      : extended;function  Pos          (    substr,         s             : string)                             : longint; function  Replicate      (    c : char;       I             : longint)                              : string;function  Round          (    e : extended)                                                       : longint;procedure SetArrayLength (var v : array;      i             : Integer); procedure SetLength      (var s : string;     L             : longint);function  Sin            (    e : extended)                                                         : extended;function  Sqrt           (    e : extended)                                                       : extended;function  StrGet       (    s : string;     I             : Integer)                            : char;function  StrSet         (    c : char;       I           : Integer; var s             : string)  : char;function  StrToFloat     (    s : string)                                                         : extended;function  StrToInt       (    s : string)                                                         : longint;function  StrToIntDef    (    s : string;     def           : longint)                            : longint;function  stringOfchar   (    c : char;     I             : longint)                              : string; function  Trim           (    s : string)                                                         : string;function  Trunc          (    e : extended)                                                       : longint; function  Uppercase      (    s : string)                                                         : string; In the above list the function name is usually sufficient to figure out what the function does. Synedit functions Unlike the PascalScript functions, the exact operation of a SynEdit operation may require some trial and error.  At this time, I am not aware of a description of how each function/key command operates.  Quote this list of SynEdit functions/key commands was obtained from                                                                                       } https://gitlab.com/freepascal.org/lazarus/lazarus/- /blob/main/components/ideintf/idecommands.pas                                                                            } which, at the time of this writing is about 5 months old                  see syneditkeycmds.pp for the most recent list                          // search   ecFind   ecFindAgain   ecFindNext   ecFindPrevious   ecReplace   ecIncrementalFind   ecFindProcedureDefinition   ecFindProcedureMethod   ecGotoLineNumber   ecFindNextWordOccurrence   ecFindPrevWordOccurrence   ecFindInFiles   ecJumpToNextSearchResult   ecJumpToPrevSearchResult   ecJumpBack   ecJumpForward   ecAddJumpPoint   ecViewJumpHistory   ecJumpToNextError   ecJumpToPrevError   ecProcedureList   // search code   ecFindDeclaration   ecFindBlockOtherEnd   ecFindBlockStart   ecOpenFileAtCursor   ecGotoIncludeDirective   ecJumpToSection   ecJumpToInterface   ecJumpToInterfaceUses   ecJumpToImplementation   ecJumpToImplementationUses   ecJumpToInitialization   ecJumpToProcedureHeader   ecJumpToProcedureBegin   // edit selection   ecSelectionUpperCase   ecSelectionLowerCase   ecSelectionSwapCase   ecSelectionTabs2Spaces   ecSelectionEnclose   ecSelectionComment   ecSelectionUncomment   ecSelectionSort   ecSelectionBreakLines   ecSelectToBrace   ecSelectCodeBlock   ecSelectWord   ecSelectLine   ecSelectParagraph   ecSelectionEncloseIFDEF   ecToggleComment   // insert text   ecInsertCharacter           // not used, moved to external package   ecInsertGUID   ecInsertFilename   ecInsertUserName   ecInsertDateTime   ecInsertChangeLogEntry   ecInsertCVSAuthor   ecInsertCVSDate   ecInsertCVSHeader   ecInsertCVSID   ecInsertCVSLog   ecInsertCVSName   ecInsertCVSRevision   ecInsertCVSSource   ecInsertGPLNotice   ecInsertGPLNoticeTranslated   ecInsertLGPLNotice   ecInsertLGPLNoticeTranslated   ecInsertModifiedLGPLNotice   ecInsertModifiedLGPLNoticeTranslated   ecInsertMITNotice   ecInsertMITNoticeTranslated   // source tools   ecWordCompletion   ecCompleteCode   ecIdentCompletion   ecSyntaxCheck   ecGuessUnclosedBlock   ecGuessMisplacedIFDEF   ecConvertDFM2LFM   ecCheckLFM   ecConvertDelphiUnit   ecConvertDelphiProject   ecConvertDelphiPackage   ecConvertEncoding   ecMakeResourceString   ecDiff   ecExtractProc   ecFindIdentifierRefs   ecRenameIdentifier   ecInvertAssignment   ecShowCodeContext   ecShowAbstractMethods   ecRemoveEmptyMethods   ecRemoveUnusedUnits   ecUseUnit   ecFindOverloads   ecFindUsedUnitRefs   ecCompleteCodeInteractive   // file menu   ecNew   ecNewUnit   ecNewForm   ecOpen   ecRevert   ecSave   ecSaveAs   ecSaveAll   ecClose   ecCleanDirectory   ecRestart   ecQuit   ecOpenUnit   ecOpenRecent   ecCloseOtherTabs   ecCloseRightTabs   // edit menu   ecMultiPaste   // IDE navigation   ecToggleFormUnit   ecToggleObjectInsp   ecToggleSourceEditor   ecToggleCodeExpl   ecToggleFPDocEditor   ecToggleMessages   ecToggleWatches   ecToggleBreakPoints   ecToggleDebuggerOut   ecViewUnitDependencies   ecViewUnitInfo   ecToggleLocals   ecToggleCallStack   ecToggleSearchResults   ecViewAnchorEditor   ecViewTabOrder   ecToggleCodeBrowser   ecToggleCompPalette   ecToggleIDESpeedBtns   ecViewComponents   ecToggleRestrictionBrowser   ecViewTodoList   ecToggleRegisters   ecToggleAssembler   ecToggleDebugEvents   ecViewPseudoTerminal   ecViewThreads   ecViewHistory   ecViewMacroList   ecToggleMemViewer   // sourcenotebook commands   ecNextEditor   ecPrevEditor   ecMoveEditorLeft   ecMoveEditorRight   ecMoveEditorLeftmost   ecMoveEditorRightmost   ecNextSharedEditor   ecPrevSharedEditor   ecNextWindow   ecPrevWindow   ecMoveEditorNextWindow   ecMoveEditorPrevWindow   ecMoveEditorNewWindow   ecCopyEditorNextWindow   ecCopyEditorPrevWindow   ecCopyEditorNewWindow   ecPrevEditorInHistory   ecNextEditorInHistory   ecGotoEditor1   ecGotoEditor2   ecGotoEditor3   ecGotoEditor4   ecGotoEditor5   ecGotoEditor6   ecGotoEditor7   ecGotoEditor8   ecGotoEditor9   ecGotoEditor0   ecLockEditor   // marker   ecSetFreeBookmark   ecPrevBookmark   ecNextBookmark   ecClearBookmarkForFile   ecClearAllBookmark   ecGotoBookmarks   ecToggleBookmarks   // Macro   ecSynMacroRecord   ecSynMacroPlay   // run menu   ecCompile   ecBuild   ecQuickCompile   ecCleanUpAndBuild   ecBuildManyModes   ecAbortBuild   ecRunWithoutDebugging   ecRun   ecPause   ecStepInto   ecStepOver   ecStepToCursor   ecStopProgram   ecResetDebugger   ecRunParameters   ecRunToCursor   ecRunWithDebugging   ecBuildFile   ecRunFile   ecConfigBuildFile   ecInspect   ecEvaluate   ecAddWatch   ecShowExecutionPoint   ecStepOut   ecStepIntoInstr   ecStepOverInstr   ecStepIntoContext   ecStepOverContext   ecAddBpSource   ecAddBpAddress   ecAddBpDataWatch   ecAttach   ecDetach   // 460++ : used for ecViewHistory (debugger) / ecViewMacroList   ecToggleBreakPoint   ecRemoveBreakPoint   ecToggleBreakPointEnabled   ecBreakPointProperties   // project menu   ecNewProject   ecNewProjectFromFile   ecOpenProject   ecOpenRecentProject   ecCloseProject   ecSaveProject   ecSaveProjectAs   ecPublishProject   ecProjectInspector   ecAddCurUnitToProj   ecRemoveFromProj   ecViewProjectUnits   ecViewProjectForms   ecViewProjectSource   ecProjectOptions   ecProjectChangeBuildMode   ecProjectResaveFormsWithI18n   // package menu   ecOpenPackage   ecOpenPackageFile   ecOpenPackageOfCurUnit   ecOpenRecentPackage   ecAddCurFileToPkg   ecNewPkgComponent   ecPackageGraph   ecPackageLinks   ecEditInstallPkgs   ecConfigCustomComps   ecNewPackage   // custom tools menu   ecExtToolFirst   ecExtToolLast   // tools menu   ecEnvironmentOptions   ecRescanFPCSrcDir   ecEditCodeTemplates   ecCodeToolsDefinesEd   ecExtToolSettings   ecManageDesktops   // ecManageExamples   ecConfigBuildLazarus   ecBuildLazarus   ecBuildAdvancedLazarus   // window menu   ecManageSourceEditors   // help menu   ecAboutLazarus   ecOnlineHelp   ecContextHelp   ecEditContextHelp   ecReportingBug   ecFocusHint   ecSmartHint   // designer   ecDesignerCopy   ecDesignerCut   ecDesignerPaste   ecDesignerSelectParent   ecDesignerMoveToFront   ecDesignerMoveToBack   ecDesignerForwardOne   ecDesignerBackOne   ecDesignerToggleNonVisComps   // SynEdit Plugins   //   Define fixed values for the IDE. Must be mapped to plugincommands,   //   when assigned to KeyMap.   //   Offsets are defined in KeyMapping   //   See: TKeyCommandRelationList.AssignTo   ecFirstPlugin   ecLastPlugin   // custom commands   ecLazarusLast   // TSynPluginTemplateEdit - In cell   ecIdePTmplEdNextCell   ecIdePTmplEdNextCellSel   ecIdePTmplEdNextCellRotate   ecIdePTmplEdNextCellSelRotate   ecIdePTmplEdPrevCell   ecIdePTmplEdPrevCellSel   ecIdePTmplEdCellHome   ecIdePTmplEdCellEnd   ecIdePTmplEdCellSelect   ecIdePTmplEdFinish   ecIdePTmplEdEscape   ecIdePTmplEdNextFirstCell   ecIdePTmplEdNextFirstCellSel   ecIdePTmplEdNextFirstCellRotate   ecIdePTmplEdNextFirstCellSelRotate   ecIdePTmplEdPrevFirstCell   ecIdePTmplEdPrevFirstCellSel   // TSynPluginTemplateEdit - Out off Cell   ecIdePTmplEdOutNextCell   ecIdePTmplEdOutNextCellSel   ecIdePTmplEdOutNextCellRotate   ecIdePTmplEdOutNextCellSelRotate   ecIdePTmplEdOutPrevCell   ecIdePTmplEdOutPrevCellSel   ecIdePTmplEdOutCellHome   ecIdePTmplEdOutCellEnd   ecIdePTmplEdOutCellSelect   ecIdePTmplEdOutFinish   ecIdePTmplEdOutEscape   ecIdePTmplEdOutNextFirstCell   ecIdePTmplEdOutNextFirstCellSel   ecIdePTmplEdOutNextFirstCellRotate   ecIdePTmplEdOutNextFirstCellSelRotate   ecIdePTmplEdOutPrevFirstCell   ecIdePTmplEdOutPrevFirstCellSel   // TSynPluginSyncroEdit - in celll   ecIdePSyncroEdNextCell   ecIdePSyncroEdNextCellSel   ecIdePSyncroEdPrevCell   ecIdePSyncroEdPrevCellSel   ecIdePSyncroEdCellHome   ecIdePSyncroEdCellEnd   ecIdePSyncroEdCellSelect   ecIdePSyncroEdEscape   ecIdePSyncroEdNextFirstCell   ecIdePSyncroEdNextFirstCellSel   ecIdePSyncroEdPrevFirstCell   ecIdePSyncroEdPrevFirstCellSel   ecIdePSyncroEdGrowCellLeft   ecIdePSyncroEdShrinkCellLeft   ecIdePSyncroEdGrowCellRight   ecIdePSyncroEdShrinkCellRight   ecIdePSyncroEdAddCell   ecIdePSyncroEdAddCellCase   ecIdePSyncroEdAddCellCtx   ecIdePSyncroEdAddCellCtxCase   ecIdePSyncroEdDelCell   // TSynPluginSyncroEdit - Out off cell   ecIdePSyncroEdOutNextCell   ecIdePSyncroEdOutNextCellSel   ecIdePSyncroEdOutPrevCell   ecIdePSyncroEdOutPrevCellSel   ecIdePSyncroEdOutCellHome   ecIdePSyncroEdOutCellEnd   ecIdePSyncroEdOutCellSelect   ecIdePSyncroEdOutEscape   ecIdePSyncroEdOutNextFirstCell   ecIdePSyncroEdOutNextFirstCellSel   ecIdePSyncroEdOutPrevFirstCell   ecIdePSyncroEdOutPrevFirstCellSel   // grow/shrink 92..95 (reserved)   ecIdePSyncroEdOutAddCell   ecIdePSyncroEdOutAddCellCase   ecIdePSyncroEdOutAddCellCtx   ecIdePSyncroEdOutAddCellCtxCase   // del 100 (reserved)   // TSynPluginSyncroEdit - selecting   ecIdePSyncroEdSelStart   ecIdePSyncroEdSelStartCase   ecIdePSyncroEdSelStartCtx   ecIdePSyncroEdSelStartCtxCase continued on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 16:50 Debugging macros Unfortunately, there is no macro debugger and, there isn't something as convenient as writeln either.  This makes debugging macros a bit cumbersome.  What follows are procedure definitions that aid in the debugging of macros. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { MACRO Purpose:                                                              }{                                                                           }{ provide debugging functions to be used in synedit macros                  } { HOW TO USE:                                                               }{                                                                           }{ COPY the contents of this file into the new macro's file                    } const                                       { COPY from HERE -------------- }  { debug message output control                                              }   DEBUG_OUTPUT_MESSAGES = TRUE;  DEBUG_PAUSE           = TRUE; procedure OutputDebugString            (             const InDebugString : string;             const InOutput      : boolean          );  { outputs a string if the InOutput parameter is TRUE and the setting of     }  { outputting debug messages is also TRUE.  if either one is FALSE, no       }  { output takes place                                                      } begin  { if debug messages are globally suppressed, exit                           }   if not DEBUG_OUTPUT_MESSAGES then exit;   if not InOutput              then exit;    { this message is suppressed     }   { neither globally nor specifically suppressed, emit the message            }   ShowMessage(InDebugString);end;  procedure Pause(const InPauseMessage : string);  { pauses execution by prompting the user to press any key.  No pausing will }  { occur if the global DEBUG_PAUSE is FALSE                                } var  InString : string; begin  if not DEBUG_PAUSE then exit;   if Trim(InPauseMessage) = '' then InPauseMessage := 'execution PAUSED';   InputQuery(InPauseMessage, 'press ENTER/RETURN to continue', InString); end; { ---------------------------------------- to HERE inclusive ----------- } { DON'T copy from this point on down                                        } beginend. // end of file// -------------------------------- --------------------------------------------  The above code provides 2 functions to aid in debugging. The first function OutputDebugString is very similar to its Windows counterpart.  It takes an additional parameter that determines if the string will be displayed or not.   The reason for this additional parameter is to make it easy to no longer output a debug string.  Once you are satisfied that the code is, to that point working correctly, simply set the second parameter to FALSE and the string will no longer be output.  This way, all the debugging code can remain in the macro to be used again if the macro fails in some case. The global variable DEBUG_OUTPUT_MESSAGES is to globally control the output of debug messages.  This allows leaving some important calls to OutputDebugString individually enabled but, no debug string will be displayed because the global setting prevents it.  This way, the most important calls can be left enabled and have their output shown by simply toggling the global setting value. The second function, Pause, is to as its name implies, pause the macro's execution.  This can be useful to occasionally inspect the progress of the macro.  The pausing can be globally controlled by setting the DEBUG_PAUSE to the appropriate value. Attached to this post is the code shown above. Now that we have a basic debugging facility, we can proceed to write some macros.  Learn by example. continued on next post... ETA: corrected a couple of minor typos. IMPORTANT: the description of Pause says "by prompting the user to press any key" it should say "by prompting the user to press the RETURN/ENTER key" ---------------------------------------------------------------------- 440bx · 2025-06-08 17:23 Note: all code is attached to this post.  There is no need to copy/paste code, just download the attachments. The following example is a _slightly_ modified example found at: https://wiki.lazarus.freepascal.org/Editor_Macros_PascalScript The code the macro will act upon is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$APPTYPE CONSOLE} { original example found at:                                                }{                                                                           }{ https://wiki.lazarus.freepascal.org/Editor_Macros_PascalScript            } program _align; { select the block of lines that start with "text" and end with "foo"         }  var  text   : string;    a        : Integer;   foo          : boolean;   { keep a copy of the original block to make re-running the macro easier and   }{ results repeatable.                                                         } { note that the macro does not align the variable names                       } var  text   : string;    a        : Integer;   foo          : boolean;   beginend. // end of file// -------------------------------- --------------------------------------------  Our first macro will be to align data types. After playing/running the macro, the aligned declarations will look like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  text        : string;    a           : Integer;   foo          : boolean;  The macro code that does the alignment is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   const                                       { COPY from HERE -------------- }  { debug message output control                                            }   DEBUG_OUTPUT_MESSAGES = TRUE;  DEBUG_PAUSE           = TRUE; procedure OutputDebugString            (             const InDebugString : string;             const InOutput      : boolean          );  { outputs a string if the InOutput parameter is TRUE and the setting of     }  { outputting debug messages is also TRUE.  if either one is FALSE, no       }  { output takes place                                                      } begin  { if debug messages are globally suppressed, exit                           }   if not DEBUG_OUTPUT_MESSAGES then exit;   if not InOutput              then exit;    { this message is suppressed     }   { neither globally nor specifically suppressed, emit the message            }   ShowMessage(InDebugString);end;  procedure Pause(const InPauseMessage : string);  { pauses execution by prompting the user to press any key.  No pausing will }  { occur if the global DEBUG_PAUSE is FALSE                                } var  InString : string; begin  if not DEBUG_PAUSE then exit;   if Trim(InPauseMessage) = '' then InPauseMessage := 'execution PAUSED';   InputQuery(InPauseMessage, 'press ENTER/RETURN to continue', InString); end; { ---------------------------------------- to HERE inclusive ----------- }   { since set definitions cannot use the .. (range), this function test for     }{ membership in the set ['a'..'z', 'A'..'Z', '_']                           } function IsIdent(c: Char): Boolean;begin  Result := ((c >= 'a') and (c <= 'z')) or            ((c >= 'A') and (c <= 'Z')) or            ((c >= '0') and (c <= '9')) or            (c = '_');end; { ---------------------------------------------------------- ----------------- } var  p1, p2  : TPoint;     { p1 = start of selection, p2 = end of selection      }  s1, s2  : string;   i, j, k : Integer;  begin   { SelAvail: returns TRUE when some text is selected.                       }    if not Caller.SelAvail then exit;  { no selection = nothing to do          }    { BlockBegin and BlockEnd return the selection points                      }    { BlockBegin: initial X,Y coordinate of selected text                      }   { BlockEnd  : ending  X,Y coordinate of selected text                    }    p1 := Caller.BlockBegin;   p2 := Caller.BlockEnd;    OutputDebugString('p1.x: ' + IntToStr(p1.x) + '   ' +                   'p1.y: ' + IntToStr(p1.y) + '   ' +                     'p2.x: ' + IntToStr(p2.x) + '   ' +                     'p2.y: ' + IntToStr(p2.y) + '   ',                     TRUE);    { NOTE: the original macro flipped the values of BlockBegin and BlockEnd   }   {       if it found that p1.y was greater than p2.y or p2.x was greater    }   {     than p1.x, this action does not seem to be necessary as testing    }   {       shows that BlockBegin is always "less" than BlockEnd             }    //   original code to flip values has been commented out as it does not seem   //   to be necessary   //   //   if (p1.y > p2.y) or ((p1.y = p2.y) and (p1.x > p2.x)) then   //   begin   //     p1 := Caller.BlockEnd;   //     p2 := Caller.BlockBegin;   //   end;   //   //   OutputDebugString('p1.x: ' + IntToStr(p1.x) + '   ' +   //                   'p1.y: ' + IntToStr(p1.y) + '   ' +   //                   'p2.x: ' + IntToStr(p2.x) + '   ' +   //                     'p2.y: ' + IntToStr(p2.y) + '   ',   //                     TRUE);     { the following is important to keep in mind:                            }   {                                                                        }   { point coordinates are 1 based but the Lines array is zero based          }    s1 := Caller.Lines[p1.y - 1];  // s1 = first line in the selected block   s2 := '';                      // s2 nothing    OutputDebugString('s1: ' + s1, TRUE);    { NOTE: the above statement are not necessary for the macro to operate     }   {     properly                                                         }    { the following code in the original macro is commented out as it is       }   { unnecessary for the macro to function properly                         }    // { skip over whitespace starting from p1.x                                }   //   // i := p1.x   // while (i <= length(s1)) and (s1[i] in [#9, ' ']) do inc(i);   //   // OutputDebugString('i: ' + IntToStr(i), TRUE);   //   //   // { look for a Pascal identifier                                             }   //   // j := i;   // if i <= length(s1) then   // begin   //   //   if IsIdent(s1[i]) then   //   { we are at the beginning of a Pascal identifier                         }   //   begin   //     while (i <= length(s1)) and IsIdent(s1[i]) do inc(i)   //   end   //   else   // { something that is _not_ a Pascal identifier                          }   //   //   begin   //     while (i <= length(s1)) and not(IsIdent(s1[i]) or (s1[i] in [#9, ' '])) do inc(i);   //   end   // end;   //   //   // OutputDebugString('j: ' + IntToStr(j), TRUE);   //   //   // { if we found an identifier, save it in s2                               }   //   // if i > j then s2 := copy(s1, j, i-j);  { s2 = first identifier on line     }   //   // OutputDebugString('s2: ' + s2, TRUE);     { ask the user for the token to align to (in this case, it should be a ":" }    if not InputQuery( 'Align', 'Token', s2) then exit;    OutputDebugString('s2 (token to align to): ' + s2, TRUE); { new value of s2}    { locate the token to align to in each of the selected lines               }   { this will provide align-to- token column index                            }    j := 0;   for i := p1.y to p2.y do   begin     s1 := Caller.Lines[i - 1];     k := pos(s2, s1);     if (k > j) then j := k;   end;    OutputDebugString('j (token column number to align to): ' + IntToStr(j),                     TRUE);    { if the token was not found, exit                                         }    if j < 1 then exit;    { align the tokens to the column                                         }    for i := p1.y to p2.y do   begin     s1 := Caller.Lines[i - 1];     k := pos(s2, s1);     if (k > 0) and (k < j) then     begin       Caller.LogicalCaretXY := Point(k, i);       while k < j do       begin         ecChar(' ');        { add the necessary spaces to align the token    }         inc(k);       end;     end;   end;end. // end of file// ------------------------------------------- ---------------------------------  Notice that the first part of the macro is a copy of the OutputDebugString and Pause functions mentioned in a previous post.  Since there is no include facility in macros, the code needs to be copied and pasted into every macro you wish to debug. After that, there is the function IsIdent which checks if its character parameter can be part of a valid Pascal identifier.  Note that the function does _not_ use a set because it would have been rather tedious and cumbersome to declare the set of valid characters one by one given that ".." is not available. The macro requires the lines it will act upon to be selected.  SelAvail ensures there are lines selected. p1 and p2 are set to the selection points with include (x,y) coordinates for each point. A call to OutputDebugString shows the p1 and p2 coordinate. After that, there are a good number of commented out lines.  It looks like, at one time, BlockBegin and BlockEnd might have been reversed depending on how the block of lines was selected (bottom to top instead of top to bottom.)  Testing shows that, at this time, BlockBegin is always above (or to the right) of  BlockEnd thereby making the code that flips the value unnecessary. Next, the code prompts the user for the token to align to.  For variable declarations, that is a ":". Once the token is known, a loop follows to determine the column number that will be used to align all the colons. Once the column number to align to is known, a "for" loop inserts characters in the appropriate places to achieve the alignment. Note that while the macro vertically aligns the colons, it does not align the beginning of the identifiers.   Another example will do that along with using the minimum amount of whitespace to accomplish the alignment. additional example on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 18:09 Our second macro will be to count array elements.  All code is present in this post's attachment.  No need to copy/paste anything. This macro is a slightly modified version of the one found at: https://wiki.lazarus.freepascal.org/Editor_Macros_PascalScript The code the macro will act upon is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$APPTYPE CONSOLE} program _Macro_ArrayElementCount;  const foo: array [1..] of integer = (  1, 2, 3,  4);  (* // save the original to make it easy to restore the test case.  Makes debugging// results repeatable and easier. // the macro should be invoked with the caret sandwiched between the .. and ]   const foo: array [1..] of integer = (  1, 2, 3,  4);  *) // NOTE: the macro would be easier to write and the code simpler if some//       formatting rules were imposed on how the array should be//       formatted.  For instance, an array such as://// const//   foo : array[1..] of anytype =//   (//    item1,//    item2,//    item3,//    < lots and  >//    < lots more >//    < items     >//   );//// not only makes the array definition easier to parse, it is also easier to// read.//// the array above follows these rules://// 1. there are no intervening blank lines anywhere in the definition// 2. the open parenthesis is on a line by itself// 3. each item is on a separate line// 4. the closing parenthesis and ending semicolon are on a separate line// 5. if a comment is present it must follow the comma//// as far as rule #3, if the array has only a few elements then it would be// easy to make the macro smart enough to figure out that all elements are// in one line and parse accordingly.  It is worth noting that in this case// it is rather unlikely a macro is necessary to count the elements!//// it would also be fairly simple to remove rule #1 as long as any line that// has an element has a single element and the first token is the element// itself always followed by a comma unless it is the last element.// if there are no intervening blank lines and every element is on its own// line then a macro is not necessary to count the elements, simply place// the caret on the first line then the last line and the count is:// last - first + 1.  It would be very easy to write a macro to do that.//// rule #5 implies that anything after a comma is ignored because rule #3// means there cannot be another item on the line. // formatting discipline goes a very LONG way into making the writing of macros// simpler and the macros more reliable. beginend.  // end of file// ---------------- ------------------------------------------------------------  The macro that does the element counting is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   const                                       { COPY from HERE -------------- }  { debug message output control                                            }   DEBUG_OUTPUT_MESSAGES = TRUE;  DEBUG_PAUSE           = TRUE; procedure OutputDebugString            (             const InDebugString : string;             const InOutput      : boolean          );  { outputs a string if the InOutput parameter is TRUE and the setting of     }  { outputting debug messages is also TRUE.  if either one is FALSE, no       }  { output takes place                                                      } begin  { if debug messages are globally suppressed, exit                           }   if not DEBUG_OUTPUT_MESSAGES then exit;   if not InOutput              then exit;    { this message is suppressed     }   { neither globally nor specifically suppressed, emit the message            }   ShowMessage(InDebugString);end;  procedure Pause(const InPauseMessage : string);  { pauses execution by prompting the user to press any key.  No pausing will }  { occur if the global DEBUG_PAUSE is FALSE                                } var  InString : string; begin  if not DEBUG_PAUSE then exit;   if Trim(InPauseMessage) = '' then InPauseMessage := 'execution PAUSED';   InputQuery(InPauseMessage, 'press ENTER/RETURN to continue', InString); end; { ---------------------------------------- to HERE inclusive ----------- }  procedure EmitCurrentCaretXY(const InShow : boolean);var  CurrentPt   : TPOINT; begin  { unlike Synedit that allows empty () in calls, PascalScript does not allow }  { empty () in parameter-less calls.                                       }   CurrentPt := Caller.CaretXY;     { note, no () in CaretXY call            }  with CurrentPt do  begin    OutputDebugString('CurrentPt.x: ' + IntToStr(x) + '    ' +                    'CurrentPt.y: ' + IntToStr(y),                      InShow);  end;end; procedure EmitCaretPoint            (             const InPointName : string;             const InPoint     : TPOINT;           const InShow : boolean            );begin  with InPoint do  begin    OutputDebugString(InPointName + '.x: ' + IntToStr(x) + '    ' +                      InPointName + '.y: ' + IntToStr(y),                    InShow);  end;end;  var  porigin, p1, p2    : TPOINT;  s                : string;  i,       c1, c2, b : integer;  t                : boolean;  a                  : char; begin  porigin := Caller.CaretXy;    { column and line where the caret is located  }   EmitCaretPoint('porigin', porigin, TRUE);   { look for an open parenthesis starting at the current caret location       }   ecIncrementalFind();          { can use () for parameter-less calls       }  ecChar('(');   { after finding the open parenthesis, the caret is AFTER the parenthesis,   }  { ecLeft brings it just before the parenthesis                              }   ecLeft;   EmitCurrentCaretXY(TRUE);    i  :=     1;  c1 :=     0;  c2 :=     0;  b  :=     0;  t  := FALSE;   repeat    p1 := Caller.CaretXy;     EmitCaretPoint('p1', p1, TRUE);     ecRight;     EmitCurrentCaretXY(TRUE);      s := Caller.LineAtCaret;     OutputDebugString('s: ' + s, TRUE);     p2 := Caller.CaretXy;     EmitCaretPoint('p2: ', p2, TRUE);     if (p1.y = p2.y) and (p1.x = p2.x) then break;     OutputDebugString('p2.x: ' + IntToStr(p2.x) + '  ' +                      'length(s): ' + IntToStr(Length(s)),                    TRUE);     if (p2.x > length(s)) then    begin      OutputDebugString('p2.x > lenght(s)', TRUE);       t := FALSE; // string ends in line      ecDown;      p2 := Caller.CaretXy;       EmitCaretPoint('p1', p1, TRUE);      EmitCaretPoint('p2', p2, TRUE);       if (p1.y = p2.y) then break;  // last line       OutputDebugString('p1.y <> p2.y', TRUE);       s := Caller.LineAtCaret;      while s = '' do      begin        p1 := Caller.CaretXy;        ecDown;        p2 := Caller.CaretXy;        if (p1.y = p2.y) then break;  // last line        s := Caller.LineAtCaret;      end;       if s = '' then break;      Caller.CaretX := 1;      p2 := Caller.CaretXy;    end;     { parse the array definition accounting for possible comments that may be }    { a part of it.                                                           }     { it is accounting for possible comments that may appear just about       }    { anywhere that make this macro's implementation a bit complicated.       }     if copy (s, p2.x, 2) = '//' then    begin      Caller.CaretX := length(s)+1;      continue;    end;     a := s[p2.x];     if copy (s, p2.x, 2) = '(*' then  a := '<';    if copy (s, p2.x, 2) = '*)' then  a := '>';     if (t) and (copy (s, p2.x, 2) = '''''') then    begin      a := ' ';      ecRight;;    end;     case a of      '{' : if (c2 = 0) and (not t)                  then c1 := c1 + 1;      '}' : if (c2 = 0) and (c1 > 0) and (not t)     then c1 := c1 - 1;      '<' : if (c1 = 0) and (not t)                  then c2 := c2 + 1;      '>' : if (c1 = 0) and (c2 > 0) and (not t)     then c2 := c2 - 1;      '''': if (c1 = 0) and (c2 = 0)                 then  t := not t;      '(', '[': if (c1 = 0) and (c2 = 0) and (not t) then  b := b + 1;       ')', ']': if (c1 = 0) and (c2 = 0) and (not t) then      begin        b := b - 1;        if b < 0 then break;      end;       { i has the number of array items, we found a comma and it is not       }      { part of a comment                                                   }       ',': if (c1 = 0) and (c2 = 0) and (not t)      then  i := i + 1;    end;   until FALSE;   Caller.CaretXy := porigin;  Caller.InsertTextAtCaret(inttostr(i), scamAdjust)end. // end of file// ---------------------------------------------------------------------- ------  The macro requires the caret to be located between the ".." and "]" before being run/played.  First thing is to notice that the macro starts with a copy of the debugging functions defined in a previous post.  In addition to that, it defines another 2 debugging helper functions EmitCurrentCaretXY and EmitCaretPoint because the macro does a lot of caret manipulation. The most important part of this example is that its function could have been accomplished with more ease should a few requirements on how the array is formatted been established. This macro uses very few SynEdit and PascalScript functions, yet it is not easy to follow because it caters to situations that can easily be prevented by establishing a few requirements up front. The "ideal" requirements are stated in the code the macro acts upon and for this reason they will not be repeated here. Also, the attachment has the DEBUG_OUTPUT_MESSAGES global set to FALSE because the loops cause a fair number of messages to be output.  Consider setting it to TRUE after running the macro once. The lesson to learn from this example is: a macro can be much more complicated than it really needs to be because it attempts to deal with too many situations, many of them uncommon. additional example on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 18:40 This is the last example.  This macro packs a punch! The purpose of this macro is to convert a Windows C API definition into a Pascal API definition.  In other words, the macro converts this: Code: C  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   HANDLE  IMAGEAPI  SymFindDebugInfoFile(      _In_ HANDLE hProcess,      _In_ PCSTR FileName,      _Out_writes_(MAX_PATH + 1) PSTR DebugFilePath,    _In_opt_ PFIND_DEBUG_FILE_CALLBACK Callback,      _In_opt_ PVOID CallerData      );  into this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function SymFindDebugInfoFile           (            { _in_     } InhProcess     : THANDLE;            { _in_     } InFileName       : pchar;          { _out_    } OutDebugFilePath : pchar;            { _in_opt_ } InoptCallback    : PFIND_DEBUG_FILE_CALLBACK;            { _in_opt_ } InoptCallerData  : pointer           )         : THANDLE; stdcall; external ImageHlp;  In a single hotkey! It's important to note that the C definition is exactly as it appears in the C .h file.  IOW, it is a straight, unmodified copy/paste from the .h file. The Pascal definition is the exact, unmodified, macro output. Basically, this macro is a "pocket" H2Pas program (for API definitions only!) The code the macro acts on is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$APPTYPE CONSOLE} program Macro_C_definition_to_Pascal_definition; (*    HANDLE  IMAGEAPI  SymFindDebugInfoFile(      _In_ HANDLE hProcess,    _In_ PCSTR FileName,      _Out_writes_(MAX_PATH + 1) PSTR DebugFilePath,      _In_opt_ PFIND_DEBUG_FILE_CALLBACK Callback,      _In_opt_ PVOID CallerData      );           *)  { internal intermediate products                                                 } {  array 1st column                 array 2nd column            array 3rd column }{  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv   vvvvvvvvvvvvvvvvvvvvvvvvv   vvvvvvvvvvvvvvvv } // HANDLE// SymFindDebugInfoFile//     _In_                         HANDLE                    hProcess//     _In_                         PCSTR                     FileName//     _Out_writes_(MAX_PATH + 1)   PSTR                      DebugFilePath//     _In_opt_                     PFIND_DEBUG_FILE_CALLBACK   Callback//     _In_opt_                   PVOID                       CallerData  {  array 1st column               array 2nd column            array 3rd column }{  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv   vvvvvvvvvvvvvvvvvvvvvvvvv   vvvvvvvvvvvvvvvv } // HANDLE// SymFindDebugInfoFile//     _In_                       HANDLE                      hProcess//     _In_                       PCSTR                       FileName//     _Out_                      PSTR                        DebugFilePath//     _In_opt_                     PFIND_DEBUG_FILE_CALLBACK   Callback//   _In_opt_                     PVOID                       CallerData { the final product, a Pascal API definition, is created using the tokens        }{ from the above intermediate product.                                         }  beginend.  This macro has a few requirements to operate properly: 1 - the definition must be preceded by a blank line 2 - the definition must be followed by a blank line 3 - the first definition line must be the function's return value and it must be a single word/token 4 - the second line must be the calling convention.  that line is IGNORED, this macro assumes the calling convention is ALWAYS stdcall.  if it is not, the resulting Pascal definition will need to be manually modified to specify the correct convention. 5 - the third line must have the function name immediately followed by an open parenthesis (no space between the name and the parenthesis) 6 - the entire C definition is contiguous, in other words, there are no blank lines in it 7 - parameter definitions are always contained in a single line 8 - parameter lines consist of 3 pieces, the first piece is Source- code Annotation Language (SAL) whose first character must be an underscore, the second piece must be a SINGLE WORD type identifier, the third piece must be a parameter name. 9 - the C definition must end with ");" by itself on a single line The great majority of MS C Windows definitions meet the above requirements. VERY IMPORTANT: the macro does NOT check that the above conditions are met. it is the programmer's responsibility to ensure they are met. the dll name associated with the definition is HARD CODED in the macro, this means the macro needs to be manually modified for API definitions in a different dll.  Modify the EXPORT_DLL variable which holds the dll name.  ALSO it does NOT check that SAL strings, types and identifiers are valid. garbage-in ensures garbage-out if the macro works at all. NOTE: Including the macro's code in this post caused the post limit of 20,000 characters to be exceeded.  For that reason, the macro's code will be in the next post. continued on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 19:30 The macro expects the caret to be _anywhere within_ the C definition when it is run/played. IOW, anyplace in lines 8 through 16. The macro is simply too large to be in a single post.  For this reason, it will be presented in parts to make it acceptable to the forum software. Macro's mainline: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  { -------- ------------------------------------------------------------------- }{ MAINLINE                                            ----------------------- } begin  { for reference purposes, emit the number of lines in the source file       }   OutputDebugString('LinesCount: ' + IntToStr(Caller.LinesCount),                  FALSE);   InitializeArrays();                    { initialize the arrays              }  InitializeGlobalVariables();   DetermineDefinitionBeginEnd();  RestoreCaretToOriginalLocation();  AddLinesForPascalDefinition();  SaveCdefinitionLines();  RemoveTrailingOpenParenthesisAndCommas();  TokenizeCdefinition();   { make any necessary adjustments to the disposition strings               }   RemovedUnwantedStringsFromDispositions();   DetermineProcedureOrFunction();   EmitApiName();  EmitOpenParenthesis();  DetermineWidestDispositionString();  EmitDispositions();  DetermineWidestParameterName();  EmitParameterNames();  EmitParameterTypes();  EmitClosingParenthesis();  EmitFunctionResult(); end.  two arrays hold the information needed to go from a C definition to a Pascal definition, they are: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  const  DEF_LINES_LO         =  1;  DEF_LINES_HI         = 64; var  DefinitionLines      : array[DEF_LINES_LO..DEF_LINES_HI] of string;  DefinitionLinesCount : integer; var  { this is the above array where each line has been broken into tokens       }   TokenizedLines       : array[DEF_LINES_LO..DEF_LINES_HI]                            of record    FunctionResultType           : string;  { index 1 only                  }    FunctionCallingConvention    : string;  { index 2 only                    }    FunctionName                 : string;  { index 3 only                    }     { indexes 4 and greater only                                            }     ParmDisposition            : string;  { e.g, _in_opt_                   }    ParmDispositionPrefix        : string;  { e.g, Inopt                    }     ParmType                     : string;    ParmName                   : string;  end; InitializeArrays sets all the strings in the arrays to empty. The DefinitionLines array holds the C definition, that is, lines 8 through 16. The TokenizedLines array holds the C definition broken into pieces.  Specifically, the function result type, the function calling convention, the function name and, for each parameter, the parameter disposition (i.e, _in_, _out_, _in_opt_, etc), the parameter type and the parameter name. In a "normal" Pascal program, some of these fields would be in a variant/union because the first definition line, has the result type and nothing else, i.e, no calling convention, function name, parameter info, etc.  Because of limitations in PascalScript, there is no variant.  This means that line 1 in the TokenizedLines array holds the FunctionResultType and nothing else.  Line 2 holds the calling convention and nothing else.  For each parameter, the FunctionResultType, CallingConvention and Name are empty strings. Obviously this wastes some memory but, keeps the code simple and _simplicity_ is absolutely necessary for a complex macro to operate properly. The initialization code is trivial: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  { -------- ------------------------------------------------------------------- } procedure InitializeArrays();  { initializes the Definition lines array and the Tokenized lines array      }var  i       : integer; begin  for i := DEF_LINES_LO to DEF_LINES_HI do  begin    DefinitionLines[i] := '';     with TokenizedLines[i] do    begin      FunctionResultType        := '';      FunctionCallingConvention := '';      FunctionName              := '';       ParmDisposition         := '';      ParmType                  := '';      ParmName                := '';    end;  end;end; { ----------------------------------- ---------------------------------------- } procedure InitializeGlobalVariables();begin  NewLineString := #13#10#0;   { saving the caret's original location isn't needed in this macro but, may  }  { be needed in other macros                                               }   with OriginalCaret do  begin    x := Caller.CaretX;  y := Caller.CaretY;  end;   OutputDebugString('X: ' + IntToStr(OriginalCaret.X) +                    'Y: ' + IntToStr(OriginalCaret.Y),                    FALSE);end;  Note that there are quite a few more global variables but those are initialized in the functions/procedures that use them. After initialization, the macro determines where the C API defintion starts and ends. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  procedure DetermineDefinitionBeginEnd();begin  { locate the definition's top line                                          }   for i := OriginalCaret.Y downto 0 do  { search towards the top of the file  }  begin    { proceed backwards from the current caret line up to locate the first    }    { definition line.  The first definition line is the one that follows a   }    { blank line                                                            }     { VERY IMPORTANT: the Lines array is zero based while the coordinates are }    {                 1 based, to make them be in synch, it is necessary to   }    {               subtract 1 from the coordinate.  IOW, Y = 1 is line 0   }     { ALSO: don't rely on the loop index having a reliable value when exiting }     FirstLine := i;    if Trim(Caller.Lines[i - 1]) = '' then    begin      break;   end;  end;   { we exited when we found an empty line, therefore the first line is the    }  { line that follows it.                                                     }   inc(FirstLine);     // 1 based   OutputDebugString('First line: ' + IntToStr(FirstLine),                    TRUE);   { now we need to find the last line                                         }   for i := OriginalCaret.Y to Caller.LinesCount - 1 do  begin    LastLine := i;  if Trim(Caller.Lines[i - 1]) = '' then    begin      break;    end;  end;   dec(LastLine);      // 1 based   OutputDebugString('Last line: ' + IntToStr(LastLine),                    TRUE);end;  The macro figures out where the definition starts and ends by looking for an empty line going up from the caret then going down from the caret.  It save the 1 based line index, to make it usable in Caret functions, in the FirstLine and LastLine globals. Just to be tidy, the macro puts the caret back in its original location.  This isn't really needed but, it's tidy. The following step is one I recommend be done in all macros whenever possible, which is: do not modify existing lines, instead create new lines that will hold the result of the macro.  That way, the original text isn't damaged by a malfunctioning macro. Following the above rule, the macro proceeds to add lines that will be used to hold its result (the Pascal definition).  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure AddLinesForPascalDefinition();begin  { now add the lines that will hold the API's Pascal definition              }   LastPt.Y := LastLine + 1;                           { line below last line  }  LastPt.X := length(Caller.Lines[LastLine - 1]) + 1; { + 1 = past eol        }   Caller.MoveCaretIgnoreEOL(LastPt);  // presume it's the physical caret   AddedLinesCount := (LastLine - FirstLine) + 1                 + SEPARATING_LINES_COUNT;   { the + 1 is because the caret is currently on the last line                }   for i := 1 to AddedLinesCount do  begin    Caller.InsertTextAtCaret(NewLineString, scamEnd);     inc(LastPt.Y);    Caller.MoveCaretIgnoreEOL(LastPt);  // presume it's the physical caret  end;   OutputDebugString('Added ' + IntToStr(AddedLinesCount) + ' lines.',                    TRUE);end;  After adding lines for the Pascal definition, save the C definition lines into its own array. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure SaveCdefinitionLines();  { saves the C definition lines into the Definition lines array              } var  i       : integer; begin  { save the definition lines in the definition lines array                 }   DefinitionLinesCount := 0;   for i := FirstLine - 1 to LastLine - 1 do  begin    inc(DefinitionLinesCount);     DefinitionLines[DefinitionLinesCount] := Caller.Lines[i];     OutputDebugString('Definition line[' + IntToStr(DefinitionLinesCount) + '] = ' +                      DefinitionLines[DefinitionLinesCount],                      FALSE);  end;end;  At this point it should be pointed out that every function/procedure is very simple.  Keep that in mind because that is key to create complex macros that work. Now that the C definition is safely stored in its array, proceed to "massage" it.  First step is to remove trailing open parentheses (only 1) and commas (usually more than 1). Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  procedure RemoveTrailingOpenParenthesisAndCommas();  { removes trailing open parenthesis and commas in the C definition lines.   }  { this macro assumes that the C function name is _immediately_ followed by  }  { an open parenthesis (that is, the open parenthesis is _not_ on a separate }  { line.  It also assumes that all parameters, except the last one, is       }  { immediately followed by a comma.  if those assumptions/requirements are   }  { not met, the behavior of this macro is unpredictable                      } var  i       : integer; begin  { remove any trailing open parenthesis and trailing comma that may be in    }  { the line                                                                }   for i := 1 to DefinitionLinesCount  do  begin    OutputDebugString('Last character of Definition line[' + IntToStr(i) + '] = ' +                      DefinitionLines[i][length(DefinitionLines[i])],                      FALSE);     if (DefinitionLines[i][length(DefinitionLines[i])] = ',') or       (DefinitionLines[i][length(DefinitionLines[i])] = '(') then    begin      { delete the comma or open parenthesis                                }       Delete(DefinitionLines[i], length(DefinitionLines[i]), 1);       OutputDebugString('Last character of Definition line['          +                        IntToStr(i) + '] = '                          +                      DefinitionLines[i][length(DefinitionLines[i])],                      FALSE);    end;  end;end;  Time to start another post before the forum software starts complaining again about exceeding the 20,000 character limit. continued on next post... ETA: Added the missing AddLinesForPascalDefinition() code. ---------------------------------------------------------------------- 440bx · 2025-06-08 22:42 continued from previous post... repost of the mainline for reference purposes, functions and procedures already discussed have been removed. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { --------- ------------------------------------------------------------------ }{ MAINLINE                                            ----------------------- } begin  < snip >    RemoveTrailingOpenParenthesisAndCommas();  TokenizeCdefinition();   { make any necessary adjustments to the disposition strings               }   RemovedUnwantedStringsFromDispositions();   DetermineProcedureOrFunction();   EmitApiName();  EmitOpenParenthesis();  DetermineWidestDispositionString();  EmitDispositions();  DetermineWidestParameterName();  EmitParameterNames();  EmitParameterTypes();  EmitClosingParenthesis();  EmitFunctionResult(); end.  After removing the open parenthensis and trailing commas, the C definition is clean enough to be tokenized. the TokenizeCdefinition procedure code is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TokenizeCdefinition();  { tokenizes the C definition.  It does so per line and the definition MUST  }  { be structured a specific way for the tokenization to be correct.          }var  i       : integer; begin  OutputDebugString('ENTERED TokenizeCdefinition();', FALSE);   for i := 1 to DefinitionLinesCount  do  begin    OutputDebugString('i: ' + IntToStr(i) + ' of ' + IntToStr(DefinitionLinesCount),                      FALSE);    case i of      1 : begin            TokenizedLines[i].FunctionResultType      := Trim(DefinitionLines[i]);            OutputDebugString(TokenizedLines[i].FunctionResultType,        FALSE);          end;       2 : begin            TokenizedLines[i].FunctionCallingConvention := Trim(DefinitionLines[i]);            OutputDebugString(TokenizedLines[i].FunctionCallingConvention, FALSE);          end;       3 : begin            TokenizedLines[i].FunctionName              := Trim(DefinitionLines[i]);            OutputDebugString(TokenizedLines[i].FunctionName,              FALSE);          end;      else      begin        { if the last character in the line is a semicolon then it should be  }        { the closing parenthesis followed by a semicolon ");" which is a     }      { line we are not interested in.                                    }         if DefinitionLines[i][length(DefinitionLines[i])] = ';' then        begin          OutputDebugString('); encountered. loop exited', FALSE);           break;        end;         { this is a parameter line, which consists of 3 parts.  break the     }        { line into its 3 constituent parts that are separated by spaces      }         TokenEnd   := length(DefinitionLines[i]);        TokenStart := length(DefinitionLines[i]);         TokenIndex := 3;   { index of parameter name                          }         while TokenStart > 0 do        begin          if DefinitionLines[i][TokenStart] <> ' ' then          begin            dec(TokenStart);             continue;          end;           { we are here because TokenEnd is pointing to a space, save the     }          { token in the appropriate string                                 }           case TokenIndex of            3 : begin                  TokenizedLines[i].ParmName := Copy(DefinitionLines[i],                                                   TokenStart + 1,                                                   TokenEnd - TokenStart);                   OutputDebugString('Parameter name: ' +                                  TokenizedLines[i].ParmName,                                    FALSE);                   { consume all the spaces                                  }                   while DefinitionLines[i][TokenStart] = ' ' do                  begin                  dec(TokenStart);                  end;                   TokenEnd := TokenStart; { first non-space character         }                 dec(TokenIndex);      { next token is the parameter type    }                end;             2 : begin                  TokenizedLines[i].ParmType := Copy(DefinitionLines[i],                                                   TokenStart + 1,                                                   TokenEnd - TokenStart);                 OutputDebugString('Parameter type: ' +                                  TokenizedLines[i].ParmType,                                    FALSE);                   { consume all the spaces                                  }                   while DefinitionLines[i][TokenStart] = ' ' do                  begin                  dec(TokenStart);                  end;                   TokenEnd := TokenStart;                   dec(TokenIndex);      { next token is the parameter type    }                 end;             1 : begin                  TokenizedLines[i].ParmDisposition := Copy(DefinitionLines[i],                                                     TokenStart + 1,                                                     TokenEnd - TokenStart);                   { we have to account for the possibility that the           }                  { disposition may include additional information we are not }                { interested in.  In those cases the last character of the  }                { disposition is _not_ an underscore (as it normally is)    }                   with TokenizedLines[i] do                  begin                    if ParmDisposition[length(ParmDisposition)] <> '_' then                    begin                      { locate the last underscore in the disposition         }                       LastUnderscore := LocateLastUnderscore(DefinitionLines[i]);                     OutputDebugString('Last underscore index: ' +                                      IntToStr(LastUnderscore),                                      FALSE);                       { string indexes are 1 based                            }                       ParmDisposition := Copy(DefinitionLines[i],                                            1,                {  1 based    }                                            LastUnderscore);                    end;                   { we want the disposition in lowercase                  }                     ParmDisposition := Lowercase(ParmDisposition);                    ParmDisposition := Trim(ParmDisposition);                  end;                   OutputDebugString('Parameter disposition: ' +                                  TokenizedLines[i].ParmDisposition,                                  FALSE);                   { consume all the spaces                                  }                   while (TokenStart                   > 0)   and                        (DefinitionLines[i][TokenStart] = ' ')  do                  begin                  dec(TokenStart);                     OutputDebugString('TokenStart: ' +                                    IntToStr(TokenStart),                                      FALSE);                end;                   { we are done processing this parameter line                }                   TokenStart := 0;              end;          end; { case TokenIndex of }         end; { for rsi }      end; { else : definition line greater than 3 }    end; { case i of }  end; { for i := 1 to DefinitionLinesCount  do }   OutputDebugString('EXITED  TokenizeCdefinition();', FALSE);end; The first 3 lines are trivial. The first line is a single token that is the function result.  The second line is also a single token that is the calling convention (which this macro doesn't use), the third line is also a single token (the open parenthesis has been removed) which is the function name.  All that is needed is to trim each string to have the proper token. Once the first three lines are scanned, the parameter lines need to be scanned. Parameter lines are scanned from right to left.  IOW, from the end towards the beginning because that makes it much simpler. The last identifier on a parameter line is the C parameter name.  All that is needed is to scan towards the beginning of the line until a space is found.  Once the space is found, the parameter name is simply the string from (space + 1) to the end of the line.   This is taken care of by the Copy function, see line 64. the type identifier precedes the parameter name.  Finding its ending position is a simple matter of consuming all the spaces between the parameter name and the parameter type.  Once that X coordinate (TokenEnd) is known then scanning backwards until a space is found gives the X coordinate of the starting position (TokenStart) which is used by the Copy function (see line 85) to save the parameter type. determining the parameter disposition follows the same process but, requires one additional step.  Sometimes the disposition will have parts that are not of interest, in this example, the third parameter contains "(MAX_PATH + 1)" which we are _not_ interested in.  We remove that portion by determining the X coordinate of the last underscore, once that is determined, again a copy (see line 130) saves the disposition. What's notable about this procedure is that two functions, Trim and Copy, were all that's needed to break the lines into tokens.   Part of the reason for the simplicity is that the function was not acting on text, instead it was acting on strings in an array. The TokenizeCdefinition may still leave some unwanted characters in the disposition.  In this example, while the procedure was able to remove the unwanted "(MAX_PATH + 1)", it left the "writes_" which is not part of the parameter disposition.  Removing such unwanted parts is done by RemovedUnwantedStringsFromDispositions() and a helper function.  They are: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i const  PARAMETERS_MIN_INDEX         = 4; const  { strings to be removed from the ParmDisposition                            }   UNWANTED_DISPOSITION_STRING_01 = 'writes_';  function UnwantedStrings           (            const InString  : string;              var OutPos    : integer;              var OutLength : integer           )         : boolean;  { determines if the InString parameter contains one of the undesirable      }  { disposition strings                                                     } begin  result    := TRUE;     { presume there is an undesirable string             }   OutPos    :=    0;  OutLength :=    0;   OutPos := Pos(UNWANTED_DISPOSITION_STRING_01, InString);   if OutPos <> 0 then  begin    OutLength := length(UNWANTED_DISPOSITION_STRING_01);    exit;  end;   result := FALSE;       { we get here when none of the strings were found    }end; {                                                  ------------------------ } procedure RemovedUnwantedStringsFromDispositions();   { removes unwanted strings that may appear in the parameter disposition     }  { ParmDisposition                                                         } var  i   : integer;     { index into TokenizedLines array                        }   p   : integer;     { position of unwanted string                          }  l   : integer;     { length   of     "      "                             } begin  { used a while instead of a "for" because using the index "i" caused        }  { problems even while still inside the "for" loop.                          }   for i := PARAMETERS_MIN_INDEX to DefinitionLinesCount do  begin    with TokenizedLines[i] do    begin      { look for unwanted strings in the parameter disposition                }       if UnwantedStrings(ParmDisposition, p, l) then      begin        OutputDebugString('i: ' + IntToStr(i) + '    ' +                        'p: ' + IntToStr(p) + '    ' +                          'l: ' + IntToStr(l),                          FALSE);         OutputDebugString('Parameter disposition: '                           +                          TokenizedLines[i].ParmDisposition,                        FALSE);         Delete(TokenizedLines[i].ParmDisposition, p, l);         OutputDebugString('Parameter disposition: '                           +                          TokenizedLines[i].ParmDisposition,                        FALSE);      end;    end; { with TokenizedLines[i] do }  end; { for }end;  The "UnwantedStrings" function is called by RemovedUnwantedStringsFromDispositions() to determine if the disposition contains an unwanted string and, if it does, its position and length in the string.  With that information, the unwanted string is Delete-ed from the disposition. Note that using FPC instead of PascalScript, it would be quite simple and straightforward to consolidate these 2 routines but, limitations such as not being able to declare typed constants (to create an array of unwanted strings) and PascalScript's problem with "break" and "continue" in nested loops required breaking the logic into 2 routines. In spite of that, it is worth noting again that the PascalScript's Pos and Delete functions were all that was needed to get the task done. Now that the C definition is tokenized, the last piece of information needed before we can proceed to build/emit the Pascal definition, is to determine if the C definition is a function or a procedure.  This is necessary because a procedure only needs a semicolon after the closing parenthesis whereas a function requires a colon and a result type before emitting the semicolon.  Making that determinatioin is "DetermineProcedureOrFunction()" purpose.  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure DetermineProcedureOrFunction();  { determines if the API definitions is that of a function or a procedure  } begin  FunctionApi := TRUE;    { presume it is a function                        }    if Uppercase(TokenizedLines[FUNCTION_RESULT_INDEX].FunctionResultType) = 'VOID' then  begin    { presume that procedures are declared as void functions                }     FunctionApi := FALSE;    exit;  end;end;  All that's needed to implement that function is to use PascalScript's Uppercase function. Now we have all the information necessary to start emitting the Pascal definition. First, we emit the function name, using EmitApiName() Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  { the Pascal API output is relative to the LastLine                         }   FirstLine            : integer;  LastLine             : integer; const  { number of lines we'll use to separate the C definition from the Pascal    }  { definition                                                              }   SEPARATING_LINES_COUNT    = 2;   API_NAME_LINE             = 1;  API_OPEN_PARENTHESIS_LINE = 2;  API_FIRST_PARAMETER_LINE  = 3;   FUNCTION_TOKEN            = 'function ';  PROCEDURE_TOKEN           = 'procedure '; procedure EmitApiName();  { outputs the function/procedure name line                                } var  OutputPt  : TPOINT; begin  with OutputPt do  begin    x := 1;    y := LastLine + SEPARATING_LINES_COUNT + API_NAME_LINE;  end;   Caller.MoveCaretIgnoreEOL(OutputPt);   case FunctionApi of    TRUE:    begin      Caller.InsertTextAtCaret(FUNCTION_TOKEN, scamEnd);    end;     FALSE:    begin      Caller.InsertTextAtCaret(PROCEDURE_TOKEN, scamEnd);    end;  end;   { now put the function name                                               }   Caller.InsertTextAtCaret(TokenizedLines[FUNCTION _NAME_INDEX].FunctionName,                           scamEnd);end; { procedure EmitApiName(); }  The notable characteristic about the Emit... family of functions is that they use PascalScript's functions   Caller.MoveCaretIgnoreEOL and Caller.InsertTextAtCaret to place the caret where needed and then put text at that location.  Every line is identified using a mix of the variable LastLine (of C definition) + the number of separating lines from the C definition to the Pascal definition + a constant that identifies the particular line that is being emitted. Generally speaking, the Emit... family a function all have a similar pattern. continued on next post... ---------------------------------------------------------------------- 440bx · 2025-06-08 23:55 continued from previous post... repost of the mainline for reference purposes, functions and procedures already discussed have been removed. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { --------- ------------------------------------------------------------------ }{ 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. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i 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: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  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. ---------------------------------------------------------------------- 440bx · 2025-06-09 12:08 In the array element counting example, I mentioned that requiring the array to have one item per line would significantly simplify determining how many elements there are in the array.  Basically a simple line count from first to last element would be all that's needed. This gave me the idea of writing a macro that simply reports the number of lines selected, which is something I've personally needed on a number of occasions. The macro is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  p1, p2 : TPOINT; begin  p2 := Caller.BlockEnd;  p1 := Caller.BlockBegin;   case Caller.SelAvail of    TRUE:    begin      ShowMessage('lines: ' + IntToStr(p2.y - p1.y + 1));    end;     FALSE:    begin      ShowMessage('zero lines selected');    end;  end;end.                 This macro counts all selected lines, it would be easy to modify it to ignore blank lines.  Hint: use a "for" loop and the "Trim" function. Note that if a line is _partially_ selected, it still counts as one fully selected line. HTH. ---------------------------------------------------------------------- n7800 · 2025-06-09 17:13 Nice tutorial! I hope this is a draft for a future wiki page? There is a tutorials category there. ---------------------------------------------------------------------- 440bx · 2025-06-09 17:29 Quote from: n7800 on June 09, 2025, 07:13:12 pmNice tutorial! Thank you n7800 Quote from: n7800 on June 09, 2025, 07:13:12 pmI hope this is a draft for a future wiki page? There is a tutorials category there. I was looking into that but, I've run into a little problem.  I forgot what my password for the wiki is.  I asked for a password reset email twice and didn't get anything.  I figured that maybe I didn't have a wiki  account so I tried to create one and was told my user name (440bx) is already in use (I doubt someone else used that handle, it would be the first time.) I figured I'd wait a few more hours to see if by any chance I get a password reset email but, I really doubt that's going to happen since they usually come within seconds. Bottom line is, I'll definitely consider putting the information on the wiki but, somehow I got to get in there first. ---------------------------------------------------------------------- n7800 · 2025-06-09 18:37 Quote from: 440bx on June 09, 2025, 07:29:56 pmI figured I'd wait a few more hours to see if by any chance I get a password reset email but, I really doubt that's going to happen since they usually come within seconds. Unfortunately, the wiki has always had access issues. Apparently, like many others, you were accidentally blocked... There are instructions on who to write to to fix the problem. You can write a personal message on this forum or via the mailing list. ---------------------------------------------------------------------- 440bx · 2025-06-09 19:28 Thank you @n7800, I will attempt to have the problem resolved.