Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


darnocian last won the day on July 30

darnocian had the most liked content!

Community Reputation

51 Excellent

1 Follower

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Hi delphi fans, I'm never sure how much attention is paid to the 'upvotes' on issues in quality portal... Anyways, as there has been considerable effort in getting LSP working and improving that by the Emb team, one feature I'd really like to see the IDE provide is a 'rad' experience from the code editor with auto completion of anonymous method signatures, etc. https://quality.embarcadero.com/browse/RSP-27293 and https://quality.embarcadero.com/browse/RSP-24310 to summarize, as an example: type TMyfunction = reference to procedure (const AArg:string); begin var func : TMyfunction := /* press ctrl+space which completes the following: */ procedure (const AArg:string) begin end; end; There can be many different ways in which the context of the type can be known based on the cursor position when ctrl+space is pressed... If you have some time and think it would also be valuable, please upvote... I don't know if the moderators should consider a separate forum section for these types of requests. regards, Conrad
  2. Hi @Edwin Yip I'm not sure why '<% getdata("somevalue", sharedstate) %>'; would be difficult? from a user perspective (someone you may have creating a template), they may not have to know about the parameter types - they just need to know what parameters are available to be passed in... I do understand however that you may have non technical users from which you may want to hide this detail. At this stage, the only way is to use class variables - which is what would be available to the class functions. Maybe a potential enhancement would be to allow the template context to be param 1: class function dosomething(const ACtx:ITemplateContext;const AFirstArg:string):string; From a usage perspective, the template engine could expect the following usage: <% dosomething('hello') %> where it auto inserts the context. The reason I mention the context is because it allows you to store data in there as well which can generally be accessed by the template.
  3. Hi @Edwin Yip As functions are static, it means that you must pass all required data to them. This will further ensure they are in a way thread safe, unless you are referencing some data that is 'shared' across threads... So normal solutions apply: 1. get a copy before hand of whatever is required 2. locking while the template is potentially referencing that shared data doing 2. is probably the most expensive, but if you are referencing shared state, there are some as to the shared state changing or not. if it doesn't, well, then it shouldn't be a problem. This may be the case say with static config loaded at startup that may be referenced during rendering. if the shared state does change - say some service methods mutate the state, then you may need some locking to ensure the underlying structures are not corrupted. with the shared state,you probably have a method that is used generally in anycase with locking for the multi threaded scenario. so exposing that function to the template is possible as well... so you may do the following: 1. pass in a reference to the static data when rendering the template 2. register a method with the template so that it can extract relevant information from the shared state e.g. type TMyProg = class private class var FLock:TCriticalSection; class constructor Create; // init flock class destructor Destroy; // free flock public class function getdata(const AKey:string; const AState:TDictionary<string, string>):string; static; end; TMyData = record SharedState : TDictionary<string,string>; end; // ------------------- class function TMyProg.getdata(const AKey:string; const AState:TDictionary<string, string>):string; begin FLock.Acquire; try if not AState.TryGetValue(akey, result) then exit('-'); finally FLock.Release; end; end; // ------------------- var GState:=TDictionary<string, string>.Create(); var GTemplate := '<% getdata("somevalue", sharedstate) %>'; var GCtx := template.context; // context could be global as well, so you don't have to keep on creating it GTtx.AddFunctions(TMyUtilities); var GTpl := Template.Parse(GCtx, GTemplate); // template could be global as well // local state var LData : TMyData; LData.SharedState := GState; writeln(Template.Eval(GCtx, GTpl, LData); // this should be localised I havn't thrown the above into an IDE, but it should work in principle... (code should be placed in relevant procedures and GState should be seen as global, etc...) I hope the above makes sense...
  4. I did a quick stab at option 2 approach (delphi first)... with this working, could now apply the logic to the template engine (another post may follow): type TMenuPos = (mpFirst, mpLast); TMenuPosInfo = set of TMenuPos; TMenuItem = class(TMenuItemCollection) private FName: string; FPosition: TMenuPosInfo; public constructor Create(const AName: string = ''); property Name: string read FName; property Position: TMenuPosInfo read FPosition write FPosition; end; TMenu = TMenuItem; function GetHtmlStack(const AMenu: TMenu): string; var lStack: TStack<TMenuItem>; lParentStack: TStack<TMenuItem>; LItem: TMenuItem; LChildItem: TMenuItem; LSB: TStringBuilder; i: integer; begin if AMenu.Children.Count = 0 then exit(''); lStack := TStack<TMenuItem>.Create; lParentStack := TStack<TMenuItem>.Create; try LSB := TStringBuilder.Create; try lStack.Push(AMenu); while (lStack.Count > 0) do begin LItem := lStack.Pop; if AMenu <> LItem then begin if mpFirst in LItem.Position then LSB.Append('<ol>'); LSB.Append('<li>'); LSB.Append(LItem.Name); end; if LItem.Children.Count > 0 then begin lParentStack.Push(LItem); for i := LItem.Children.Count - 1 downto 0 do begin lStack.Push(LItem.Children[i]); end; continue; end; LSB.Append('</li>'); if mpLast in LItem.Position then begin LSB.Append('</ol>').Append('</li>'); lParentStack.Pop; end; end; while lParentStack.Count > 0 do begin LItem := lParentStack.Pop; lsb.Append('</ol>'); if lParentStack.Count > 0 then LSB.Append('</li>'); end; exit(LSB.ToString); finally LSB.free; end; finally lParentStack.free; lStack.free; end; end; this seems to produce the same as the functionally reclusive function.
  5. Yes, you can do something like the following: begin var ctx := Template.Context; ctx.StartToken := '{{'; ctx.EndToken := '}}'; Assert.IsEqual('hello', Template.Eval(ctx, '{{ if true }}hello{{else}}bye{{end}}')); end;
  6. Yes. I've illustrated option1 to start... as mentioned, I'll follow up on option 2... personally, I don't think there has to be an absolute rule about where presentation logic is... ideally we want it constrained to the presentation layer where possible... going the option2 route can make the template less pleasant to read, and that is sort of what the helper functions are there to do, besides providing additional bridging.... Also note - the option1 route may be faster from a CPU perspective (just mentioning)... anyways, I'll present that example in a few days when I get some time too look at it again.
  7. Let say there is some type stuff looking like follows: type TMenuItem = class; TMenuItemCollection = class private FChildren: TObjectList<TMenuItem>; public constructor Create(); destructor Destroy; override; function AddChild(const AName: string): TMenuItem; property Children: TObjectList<TMenuItem> read FChildren; end; TMenuItem = class(TMenuItemCollection) private FName: string; FLast: boolean; public constructor Create(const AName: string = ''); property Name: string read FName; property Last: boolean read FLast write FLast; end; TMenu = TMenuItem; With traditional recursion, you can do the following: procedure GetHtmlRecursive(const AMenu: TMenu; const ASB: TStringBuilder); forward; overload; procedure GetItemHtmlRecursive(const AMenuItem: TMenuItem; const ASB: TStringBuilder); var LItem: TMenuItem; begin ASB.Append('<li>'); ASB.Append(AMenuItem.Name); if AMenuItem.Children.Count > 0 then begin GetHtmlRecursive(AMenuItem, ASB); end; ASB.Append('</li>'); end; procedure GetHtmlRecursive(const AMenu: TMenu; const ASB: TStringBuilder); overload; var LItem: TMenuItem; begin if AMenu.Children.Count = 0 then exit; ASB.Append('<ol>'); for LItem in AMenu.Children do begin GetItemHtmlRecursive(LItem, ASB); end; ASB.Append('</ol>'); end; function GetHtmlRecursive(const AMenu: TMenu): string; overload; var LSB: TStringBuilder; LItem: TMenuItem; begin if AMenu.Children.Count = 0 then exit(''); LSB := TStringBuilder.Create; try GetHtmlRecursive(AMenu, LSB); exit(LSB.ToString); finally LSB.free; end; end; To test, we can try something like: var lmenu: TMenu; lchild: TMenuItem; lchild2: TMenuItem; lchild3: TMenuItem; begin lmenu := TMenu.Create; try lchild := lmenu.AddChild('a'); lchild2 := lchild.AddChild('a1'); lchild3 := lchild2.AddChild('a1a'); lchild3 := lchild2.AddChild('a1b'); lchild2 := lchild.AddChild('a2'); lchild2 := lchild.AddChild('a3'); lchild := lmenu.AddChild('b'); lchild2 := lchild.AddChild('b1'); lchild2 := lchild.AddChild('b2'); lchild3 := lchild2.AddChild('b2a'); lchild3 := lchild2.AddChild('b2b'); lchild2 := lchild.AddChild('b3'); var lstr := GetHtmlRecursive(lmenu); writeln(lstr); finally lmenu.free; end; end; So the above just demonstrates how a menu could be done via delphi code... So next, we want to try test this from the template... I havn't got to test this for myself with the above code yet, but you should be able to do the following: type TMyTemplateData = record Menu : TMenu; Data : TData; // whatever end; TTemplateUtils = class public class function RenderMenu(const AMenu: TMenu) : string; static; // call the function above, or just move that in here end; var LMyData:TMyTemplateData; LCtx : TTemplateContext; begin LMyData.Menu := ... some setup ..; LMyData.Data := ... some setup ...; LCtx := TTemplateContext.Create; LCtx.Functions.addfunctions(TTemplateUtils); writeln(Template.Eval(LCtx,'Here is my menu:<br><% RenderMenu(Menu) %>', LMyData); end;
  8. recursive functions call themselves and helper functions utilising the normal stack by managing CPU stack registers. It is often easier to do things this way as well, but there is also a scenario where you can have a stack overflow, depending on how much memory is accessible by the stack. From an algorithmic perspective, it is possible to transform functionally recursive functions to utilise a developer managed stacks, which are used to backtrack appropriately. last night, I wrote a quick recursive one that I can share. I'm a bit busy now, so might only get to the 'dev stack' bound one over the weekend or next week. Let me know if you want me to share stuff earlier.
  9. The template engine is primarily focused on rendering output where data is provided by variables passed in. It does support calling functions written in Delphi. I think you could do two ways: have a function that does the recursion and returns the relevant html (so complexity is in the function in delphi). pro: template will appear simpler con: some presentation is offloaded to the function you can pass the menu structure to the template. you could use a stack in the template, which would allow you to process the structure in a recursive way using a stack, rather than relying on function recursion with a function stack. pro: all presentation is in the template con: the template may look a bit more complex I think I prefer option 2. As the template engine will not know offhand how to create a stack, the easiest is to have a structure containing menu and stack which is passed to the template.
  10. darnocian

    web scraping or web parsing for project?

    Maybe it is simpler to imagine this way: - downloading the content from websites = scraping - inspecting that content and extracting something meaningful = parsing so when you scrape a website, the two concepts work hand in hand. get the content, extract anything meaningful, identify if there are any links, repeat... parsing isn't an undocumented subject. it is often taught in courses on compiler theory ... but doesn't necessarily have to relate to compilers... also, the way content can be parsed can vary: - you can be formal and parse according to some language's grammatical rule set - you can cheat - if you know something about the content, you could do some pos() + copy() to get what you want out of it - you could even use regular expressions to extract data based on patterns you may be interested in... e.g. emails or urls can be easily identifiable
  11. darnocian

    Console Manager for the Delphi IDE

    agreed. I think the newer api should be better for doing this sort of thing.
  12. darnocian

    Console Manager for the Delphi IDE

    I know this topic area isn't supposed to be commercial. Would it be reasonable to charge a small $5-$10 to build and support this? Open to ideas on the matter.
  13. darnocian

    Console Manager for the Delphi IDE

    Thanks Edwin. I did mention it was a POC... and am aiming for the full 'embedded' experience. 😉 There isn't really a 'directly embedding' option from what I've researched, but open to any input on the matter. Essentially there are 2 ways I've read about: - the legacy apis using the 'old style' win32 apis - what I've essentially seen happen is the guys hide the actual console that is created, and scrape the console buffer, console size and cursor information, and then redraw it. seems convoluted, but I believe this is what the cmd.exe hosting window does. I read this a few days ago, but can't recall the exact source. (and maybe I'm wrong - I'm open to that too) 😉 - the 'new style' using the pseudo terminal apis - as Microsoft has embraced the unix world, the new pseudo terminal apis are aimed at bridging the legacy and unix philosophies... I'm still reading... I started so far creating bindings for some of the missing api functions from windows.pas etc... I'll probably go down the 'new' way to future proof things, but the current version tickles my immediate itch, but I know there is more scratching on its way. If there are simpler approaches, I'm open to hearing about them. There are some 3rd party libs used in other IDEs like WinPty... I have not explored it yet, but also not sure if the additional dependency is really needed, except from a saving time perspective if it proves easy to do and does the job well. i wanted to have a 'list' of consoles, as these windows can go invisible, so something needs to manage them. I wanted to add a menu item in the right-click 'project structure'... but that menu is already so long... But maybe on the main 'Project' menu in the IDE, the item could be for the current project. Although that may require an additional popup to select which terminal. I'm not totally happy with my current UI behaviour in the current implementation as it presents a popup to give the window a name. if I present an option as a menu item, then a may want to have 2 options - start a powershell.exe, start a cmd.exe... the list can just get automatic naming, and people could rename afterwards if they desire.
  14. darnocian

    Console Manager for the Delphi IDE

    I was not sure if the docking thing would be a hard or not, but decided to give it a shot... and result! Thanks for the feedback.