Jump to content

darnocian

Members
  • Content Count

    106
  • Joined

  • Last visited

  • Days Won

    2

Everything posted by darnocian

  1. Hi all, I'm pleased to announce the new release (v1.7.0) of the Sempare Template Engine. It has been a while, and thought I'd just remind any users here about the project. Also included are some demos... for apps in general, there is a new helper class TTemplateRegistry, which should make things much easier to just get going and supports template reloading without restarting the app and can load templates from file, resources, or custom a loader. This takes some of the grunt work out of working with templates, besides just using them. More information is available in the docs on the repo. Some simple examples are provided to illustrate server side scripting. If you have any suggestions, or see any gaps, please feel free to provide comments on the issue tracker. - Demo using the Horse framework - Demo using WebBroker standalone console app New features: - improved support for comments - extends/block template support - improved whitespace removal - renamed demo app to Sempare Template Engine Playground - a useful tool for testing language features / templates. - added the Sempare.Template.RCGenerator project to help with creating resource rc files by scanning directories. - support multiple statements in a script block. e.g. <% print('a'); print('b') %> - additional context configuration - improved docs (improved navigation and added railroad diagrams to illustrate the grammar) Older release notes can be viewed on the releases page. Have fun.
  2. darnocian

    Using WriteLn in dll

    Maybe better to have a callback function that can be used for debugging. That would be controllable by the application, UI or console. e.g. in the DLL, have a proceudure SetDebugCallback(const ACallback:TDebugCallback); where TDebugCallback = procedure (const AMessage: PChar); stdcall; Note: use PChar and not a string (maybe be even more explicit like PWideChar, PAnsiChar). That way, in the application, console app could do writeln, where the UI app could show a dialog, add to a list, whatever... Also, I wouldn't trust sending a Delphi managed type across a DLL boundary. It limits what type of apps can consume and use the function as they would need to emulate the string interface, knowing the internals of the type, where using zero terminated strings is supported by the Delphi compiler.
  3. I could assume that if the structure in question is more complex and the snippet we see is a sample, the use of withValue() would be part of a bigger set of stuff where the structure is set using method chaining... e.g. myrec.withValue('abc').withNum(123)... returning a pointer would be slightly more optimal as the delphi syntax should still allow the chaining with the '.', and the original structure would be referenced rather than copies being made if the result of each withXyz() was an actual record. if part of the question is about record vs class, well records in Delphi can be heap or stack based, where classes in Delphi are heap based. Assignment of a record in general, even in a method chain would result in copies, vs using a class would simply be passing a pointer around. As David mentioned however, it is a trivial scenario, just using a constructor would do. Using method chaining is sometimes an opinion piece, commonly used when using builder type patterns.
  4. darnocian

    App for docker

    Using fork() is the Linux way, but you don't have to worry about that in a docker environment. In Docker, you can simply rely on creating a simple console app. You then just rely on Docker Swarm or Kubernetes to be running on the host machine running the container as required. You simply need to provide a CMD or ENTRYPOINT in the Dockerfile referencing your command line application. I forgot you mentioned NGINX... So it depends how 'pure' you want to be... the 'pure' way is you have 2 containers: 1) nginx and 2) your app.... However, there is nothing stopping you from creating one container with NGINX and your application in it, but it is a container anti-pattern.
  5. My point was less about how many tools can consume it, but about having something that could actually be used at the end of the day. It could be anything, I just happen to like ANTLR more compared to lex/yacc and flex/bison, which I used to use in the old days. I'm not sure 100% of our objective - do we just want to have a text document (which is valuable in its own right), or do we want to have a grammar that can be consumed by a tool as well?
  6. I mentioned ANTLR before (https://www.antlr.org/). I'm just wondering if it would not be worth adopting a syntax like that that can be consumed and used in tooling instantly? e.g. https://github.com/fabriciocolombo/sonar-delphi/blob/master/src/main/antlr3/org/sonar/plugins/delphi/antlr/Delphi.g (a version 3 grammar) https://github.com/gotthardsen/Delphi-ANTRL4-Grammar/blob/master/Delphi.g4 Unfortunately, there isn't a 'runtime' for delphi on the latest version which is very mature, but using another language run time, like java, c#, etc, we could easily make a simple parser for delphi, until someone has time to work on one for delphi.
  7. Re BNF vs diagrams… as the format should be formal, it would allow for diagrams to be created easily using tools like graphgviz/dot and sexier/modern versions thereof. Re benefits of a grammar it can be used as a basis for all kinds of things… once a model is created using a parser generator or manually crafted, you can create things that might be out of scope of the actual compiler. E.g. static code analysis, documentation generation, dependency analysis (between units), search tools, creating diagrams, etc. Writing parsers can take a bit of effort, and to do it correctly, the grammar needs to be right. There is a lot of boiler plate (repeated patterns) when it comes to parsing, so over time people have written parser generation tools to assist with this process more optimally (and avoid bugs). Their input would normally be a bnf type config file. Examples of such tools is yacc (yet another compiler compiler), etc. I personally like the ANTLR framework. Unfortunately, the latest version 4 doesn’t have a Delphi runtime, but version 3 had one. it would be convenient to update this to fit in with one of those tools as it also makes it easier to instantly validate and create test cases on the grammar.
  8. darnocian

    Class properties

    The issue is that for anything 'virtual' to work, a VMT needs to be available. Virtual methods are accessible on instances of a class through the class VMT. The only way to accomplish something like what you wanted would be to introduce some helper variables/procedures in delphi/pascal to accomplish this - something that is overly verbose in pascal compared to python. I've given an example below. From a python perspective, you would maybe have something like: class X: @staticmethod def do_something(): return '42' print(X.do_something()) # 42 # illustrating overriding with a lambda X.do_something = lambda: '43' print(X.do_something()) # 43 In Delphi, you could do the following: type TMyGetter = function : string; TTest = class private class var FValueGetter: TMyGetter; class function GetValue:string; public class constructor Create; class property ValueGetter: TMyGetter read FValueGetter write FValueGetter; class property Value : string read GetValue; end; function MyGetValue:string; begin exit('42'); end; function MyGetValue2:string; begin exit('43'); end; class constructor TTest.Create; begin TTest.FValueGetter := MyGetValue; end; class function TTest.GetValue:string; begin if assigned(TTest.FValueGetter) then begin result := TTest.FValueGetter(); exit; end; raise Exception.Create('no getter set'); end; begin writeln(TTest.Value); // 42 TTest.ValueGetter := MyGetValue2; writeln(TTest.Value); // 43 end. In the above the the 'ValueGetter' sort of provides the same behaviour of the VMT in allowing it to be overridden. However, this is not a polymorphic property - so inheritance does not work... child classes would essentially modify the same static variable - but this principle applies the same way for my python example above as well I think.
  9. darnocian

    task thread priority?

    You may know this, or for other readers... As Lars mentioned, 'waiting' correctly is important. Ideally we should not be sleep()ing before querying a queue. Be mindful not to create 1 thread per request. If there are too many threads, then your scheduler ends up spending more time spinning, which doesn't help. Ideally, it should be utilising thread synchronisation primitives such as TEvent SetEvent() and WaitFor() when adding requests to the queue and waiting for events in the queue respectively. Further, we need to ensure we have some locking around whatever structure is used for the queue so that concurrent access doesn't destroy the structure's consistency. I'll write a little example later.
  10. darnocian

    task thread priority?

    I agree with Anders. using threads can be for different reasons though… backgrounding a blocking io bound tasks is different to backgrounding a cpu bound tasks. you may want to consider thread pools, as they ensure threads are recycled efficiently, and don’t normally exceed the number of cores. On multi core systems, I’ve normally used threadpools usually helps in recycling threads. The Delphi tasks library essentially captures this concept at a high level. there is also thread affinity that can be assigned using SetThreadAffinityMask, (on windows) but not sure how effective it is. The docs also recommend letting the system do the scheduling.
  11. darnocian

    Parallel processing question

    If you use TTask.Run(), it returns an ITask which resembles the 'future' pattern - the interface provides a getStatus() to check if it is complete or not, as well as other helper methods. From OmniThreadLibrary does seem like the Parallel.Async() could be extended to return something similar. However, using ITask / IFuture requires 'polling' type behavior compared to callbacks ... but I think what Dalija has just suggested is closer to what you probably want.
  12. Hi everyone, Last week I made a Debug Configuration Manager for Delphi. It is a tool to allow for lists of debug configurations (Host App, Parameters, Work Directory, Environment Variables) to be easily managed in the IDE and be applied to an active project. It is also project aware, so the configuration settings are managed and persisted on a per project basis. I have not released this publicly yet, but attached is a short video demonstrating how it works. If you are interested in accessing it, please send me a mail: conrad.vermeulen@gmail.com I'll provide more information when it is officially released. Regards, Conrad
  13. darnocian

    Debug Configuration Manager for the Delphi IDE

    I've added installers for both Delphi 10.4.2 and Delphi 11.
  14. darnocian

    Debug Configuration Manager for the Delphi IDE

    I've uploaded an update for interested parties to try. http://downloads.sempare.ltd/SempareDebugConfigurationManager/
  15. darnocian

    How to iterate a TDictionary using RTTI and TValue

    I wish RTTI embedded the generic type parameters and the TRttiType included a way to extract them. I've often had to rely on specific use cases and knowing that I can extract the types easily from known method return types as highlighted by the enumerator / pair example above... I've had times where I needed some really generic stuff and had to split the TWhatever<T> as a string and resolve the generic parameter however it is defined recursively - which is a bit annoying.
  16. I'm not sure if think you might be thinking that 'uses' works similarly to the 'import' statement in Java. In Java, you normally import the type from the fully qualified package as you have attempted. 'System.Generics.Collections' is the unit, with many types/functions defined within. if you just needed to use it, you simply do 'uses System.Generics.Collections;' From the online help, if you see something like 'System.Generic.Collections.TArray' - it normally means that 'TArray' will be defined in 'System.Generics.Collections', so you 'uses' as illustrated above.
  17. 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
  18. 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.
  19. 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...
  20. 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.
  21. 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;
  22. 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.
  23. 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;
  24. 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.
×