From 26fff743f3380f9535b820f0e9238e1fd252b18a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sat, 25 Mar 2023 15:10:38 +0000 Subject: [PATCH 001/138] add lexer support for <%- -%>, <%* and *%> --- src/Sempare.Template.AST.pas | 8 ++ src/Sempare.Template.Lexer.pas | 106 ++++++++++++++++++--------- tests/Sempare.Template.TestLexer.pas | 11 ++- 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index dea1e91..d7cdf9e 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -53,6 +53,12 @@ ETemplate = class(Exception); boEQ, boNotEQ, boLT, boLTE, boGT, boGTE, // comparison boIN); + TStripAction = ( // + saWhitespace, // + saWhitespaceAndNL, // + saNone // + ); + TTemplateSymbol = ( // // general parsing vsInvalid, // @@ -155,10 +161,12 @@ ETemplate = class(Exception); ['{3EC6C60C-164F-4BF5-AF2E-8F3CFC30C594}'] function GetPosition: IPosition; function GetToken: TTemplateSymbol; + function GetStripAction: TStripAction; procedure SetToken(const AToken: TTemplateSymbol); function StripWS: boolean; property Token: TTemplateSymbol read GetToken write SetToken; property Position: IPosition read GetPosition; + property StripAction: TStripAction read GetStripAction; end; ITemplateVisitor = interface; diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index 618c38d..57ec10c 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -127,12 +127,14 @@ TSimpleTemplateSymbol = class(TInterfacedObject, ITemplateSymbol) FToken: TTemplateSymbol; FPosition: IPosition; FStripWS: Boolean; + FStripAction: TStripAction; function GetPosition: IPosition; public - constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean = false); + constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone); procedure SetToken(const AToken: TTemplateSymbol); function GetToken: TTemplateSymbol; function StripWS: Boolean; + function GetStripAction: TStripAction; end; TTemplateValueSymbol = class(TSimpleTemplateSymbol, ITemplateValueSymbol) @@ -247,6 +249,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; LLast: char; LEndExpect: char; LEndStripWS: Boolean; + LStripAction: TStripAction; function MakePosition: IPosition; begin @@ -256,13 +259,14 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone; const AGetInput: Boolean = True): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS); - GetInput; + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripAction); + if AGetInput then + GetInput; end; function IsValidId(const AId: string): Boolean; @@ -305,6 +309,36 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(ValueToken(vsString)); end; + function isEndOfScript(out aResult: ITemplateSymbol; const AStripAction: TStripAction; const AGetInput: Boolean = false): Boolean; + begin + if AGetInput then + GetInput; + if CharInSet(FCurrent.Input, [FEndScript[1], FEndStripScript[1]]) then + begin + if FCurrent.Input = FEndScript[1] then + LEndExpect := FEndScript[2] + else + LEndExpect := FEndStripScript[2]; + if Expecting(LEndExpect) then + begin + LEndStripWS := FCurrent.Input = FEndStripScript[1]; + GetInput; + if FAccumulator.length > 0 then + begin + aResult := ValueToken(vsText); + FNextToken := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + end + else + begin + aResult := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + end; + FState := SText; + exit(True); + end; + end; + exit(false); + end; + begin FAccumulator.Clear; LLine := FLine; @@ -386,9 +420,19 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; '+': exit(SimpleToken(vsPLUS)); '-': - exit(SimpleToken(vsMinus)); + begin + if isEndOfScript(Result, TStripAction.saWhitespace, True) then + exit + else + exit(SimpleToken(vsMinus, false, saNone, false)); + end; '*': - exit(SimpleToken(vsMULT)); + begin + if isEndOfScript(Result, TStripAction.saWhitespaceAndNL, True) then + exit + else + exit(SimpleToken(vsMULT, false, saNone, false)); + end; '/': exit(SimpleToken(vsSLASH)); '<': @@ -434,29 +478,8 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(SimpleToken(vsCOLON)); else begin - if CharInSet(FCurrent.Input, [FEndScript[1], FEndStripScript[1]]) then - begin - if FCurrent.Input = FEndScript[1] then - LEndExpect := FEndScript[2] - else - LEndExpect := FEndStripScript[2]; - if Expecting(LEndExpect) then - begin - LEndStripWS := FCurrent.Input = FEndStripScript[1]; - GetInput; - if FAccumulator.length > 0 then - begin - Result := ValueToken(vsText); - FNextToken := SimpleToken(VsEndScript, LEndStripWS); - end - else - begin - Result := SimpleToken(VsEndScript, LEndStripWS); - end; - FState := SText; - exit; - end; - end; + if isEndOfScript(Result, TStripAction.saNone) then + exit; end; end; FAccumulator.Append(FCurrent.Input); @@ -478,6 +501,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; LPosition: integer; LLastChar, LCurChar: char; LIsStartStripWSToken: Boolean; + LState: TStripAction; function MakePosition: IPosition; begin @@ -487,12 +511,12 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS); + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripAction); GetInput; end; @@ -519,8 +543,18 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; if (FCurrent.Input = FStartScript[1]) and (FLookahead.Input = FStartScript[2]) or LIsStartStripWSToken then begin Result := ValueToken(vsText); + case FLookahead.Input of + '-': + LState := TStripAction.saWhitespace; + '*': + LState := TStripAction.saWhitespaceAndNL; + else + LState := TStripAction.saNone; + end; + if LState <> TStripAction.saNone then + GetInput; FState := SScript; - FNextToken := SimpleToken(VsStartScript, LIsStartStripWSToken); + FNextToken := SimpleToken(VsStartScript, LIsStartStripWSToken, LState); exit(); end else @@ -573,8 +607,9 @@ procedure TTemplateLexer.SwallowInput; { TSimpleMustacheToken } -constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean); +constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean; const AStripAction: TStripAction); begin + FStripAction := AStripAction; FToken := AToken; FPosition := APosition; FStripWS := AStripWS; @@ -585,6 +620,11 @@ function TSimpleTemplateSymbol.GetPosition: IPosition; exit(FPosition); end; +function TSimpleTemplateSymbol.GetStripAction: TStripAction; +begin + exit(FStripAction); +end; + function TSimpleTemplateSymbol.GetToken: TTemplateSymbol; begin exit(FToken); diff --git a/tests/Sempare.Template.TestLexer.pas b/tests/Sempare.Template.TestLexer.pas index 6cb224b..bbaf3c2 100644 --- a/tests/Sempare.Template.TestLexer.pas +++ b/tests/Sempare.Template.TestLexer.pas @@ -63,7 +63,8 @@ TTestTemplateLexer = class procedure TestUnicodeQuotedString; [Test] procedure TestInvalidChar; - + [Test] + procedure TestStripCharLeftAndRight; end; implementation @@ -164,6 +165,14 @@ procedure TTestTemplateLexer.TestString; Assert.AreEqual('hello world', Template.Eval('<% ''hello'' + '' '' + ''world'' %>')); end; +procedure TTestTemplateLexer.TestStripCharLeftAndRight; +begin + Assert.AreEqual('hello world', Template.Eval('<%- ''hello'' + '' '' + ''world'' -%>')); + Assert.AreEqual('hello world', Template.Eval('<%* ''hello'' + '' '' + ''world'' *%>')); + Assert.AreEqual('123', Template.Eval('<%- 123%>')); + Assert.AreEqual('-123', Template.Eval('<%- -123%>')); +end; + procedure TTestTemplateLexer.TestUnicodeQuotedString; begin Assert.AreEqual('this is a test', Template.Eval('<% ‘this is a test’ %>')); From 2be1d6e82fc1149e18f859c2f86fd232155f7d8e Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 26 Mar 2023 23:52:09 +0100 Subject: [PATCH 002/138] initial extends support. super/inherited is not being implemented currently. this currently has a leak which needs to be resolved when freeing LBlockReplacer / LBlockResolver in TTemplateParser.ruleExtendsStmt --- src/Sempare.Template.AST.pas | 31 +- src/Sempare.Template.Evaluate.pas | 16 + src/Sempare.Template.Lexer.pas | 8 +- src/Sempare.Template.Parser.pas | 382 +++++++++++++++++++- src/Sempare.Template.PrettyPrint.pas | 32 ++ src/Sempare.Template.VariableExtraction.pas | 31 ++ src/Sempare.Template.Visitor.pas | 12 + tests/Sempare.Template.TestIf.pas | 2 + tests/Sempare.Template.TestInclude.pas | 57 +++ 9 files changed, 566 insertions(+), 5 deletions(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index dea1e91..cad978f 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -135,7 +135,10 @@ ETemplate = class(Exception); // for expression list vsComma, // - vsSemiColon // + vsSemiColon, // + + vsExtends, // + vsBlock // ); IPosition = interface @@ -172,6 +175,7 @@ ETemplate = class(Exception); ITemplateVisitorHost = interface ['{BB5F2BF7-390D-4E20-8FD2-DB7609519143}'] procedure Accept(const AVisitor: ITemplateVisitor); + function Clone: IInterface; end; IExpr = interface @@ -180,6 +184,7 @@ ETemplate = class(Exception); IStmt = interface ['{6D37028E-A0C0-41F1-8A59-EDC0C9ADD9C7}'] + function Clone: IInterface; end; IDebugStmt = interface(IStmt) @@ -193,6 +198,8 @@ ETemplate = class(Exception); function GetItem(const AOffset: integer): ITemplateVisitorHost; function GetCount: integer; function GetLastItem: ITemplateVisitorHost; + function Clone: IInterface; + property Items[const AOffset: integer]: ITemplateVisitorHost read GetItem; property Count: integer read GetCount; property LastItem: ITemplateVisitorHost read GetLastItem; @@ -203,6 +210,26 @@ ETemplate = class(Exception); procedure Add(const AItem: ITemplateVisitorHost); end; + IExtendsStmt = interface(IStmt) + ['{220D7E83-280D-454B-BA60-622C97EBE131}'] + function GetName: string; + function GetContainer: ITemplate; + procedure SetContainer(const AContainer: ITemplate); + function GetExpr: IExpr; + property Name: string read GetName; + property Expr: IExpr read GetExpr; + property Container: ITemplate read GetContainer write SetContainer; + end; + + IBlockStmt = interface(IStmt) + ['{EBAC38C4-9790-4D7C-844C-BE7C94E7C822}'] + function GetName: string; + function GetContainer: ITemplate; + procedure SetContainer(const AContainer: ITemplate); + property Name: string read GetName; + property Container: ITemplate read GetContainer write SetContainer; + end; + IContinueStmt = interface(IStmt) ['{FB4CC3AB-BFEC-4189-B555-153DDA490D15}'] end; @@ -483,6 +510,8 @@ ETemplate = class(Exception); procedure Visit(const AStmt: IWithStmt); overload; procedure Visit(const AStmt: ICycleStmt); overload; procedure Visit(const AStmt: IDebugStmt); overload; + procedure Visit(const AStmt: IBlockStmt); overload; + procedure Visit(const AStmt: IExtendsStmt); overload; end; implementation diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 06c30d0..8c2014b 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -132,6 +132,10 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IWithStmt); overload; override; procedure Visit(const AStmt: ICycleStmt); overload; override; procedure Visit(const AStmt: IDebugStmt); overload; override; + + procedure Visit(const AStmt: IBlockStmt); overload; override; + procedure Visit(const AStmt: IExtendsStmt); overload; override; + end; implementation @@ -1154,6 +1158,18 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDebugStmt); end; end; +procedure TEvaluationTemplateVisitor.Visit(const AStmt: IBlockStmt); +begin + if assigned(AStmt.Container) then + AcceptVisitor(AStmt.Container, self); +end; + +procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); +begin + if assigned(AStmt.Container) then + AcceptVisitor(AStmt.Container, self); +end; + { TNewLineStreamWriter } constructor TNewLineStreamWriter.Create(const AStream: TStream; const AEncoding: TEncoding; const ANL: string; const AOptions: TTemplateEvaluationOptions); diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index 618c38d..a0727c8 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -413,7 +413,11 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; else exit(SimpleToken(vsGT)); '=': - exit(SimpleToken(vsEQ)); + begin + if FLookahead.Input = '=' then + GetInput; + exit(SimpleToken(vsEQ)); + end; '`': exit(ReturnString('`')); '‘': @@ -687,6 +691,8 @@ initialization AddHashedKeyword('onend', vsOnEnd); AddHashedKeyword('onempty', vsOnEmpty); AddHashedKeyword('betweenitems', vsBetweenItem); +AddHashedKeyword('extends', vsExtends); +AddHashedKeyword('block', vsBlock); AddSymKeyword('ScriptStartToken', VsStartScript); AddSymKeyword('ScriptEndToken', VsEndScript); diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 184f860..7dfba6d 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -58,6 +58,8 @@ implementation System.Rtti, System.Generics.Collections, Sempare.Template, + Sempare.Template.BlockResolver, + Sempare.Template.BlockReplacer, Sempare.Template.ResourceStrings, Sempare.Template.PrettyPrint, Sempare.Template.Evaluate, @@ -75,8 +77,8 @@ TTemplate = class(TInterfacedObject, ITemplate, ITemplateAdd, ITemplateVisitor function GetCount: integer; procedure Add(const AItem: ITemplateVisitorHost); function GetLastItem: ITemplateVisitorHost; - procedure Accept(const AVisitor: ITemplateVisitor); + function Clone: IInterface; public end; @@ -89,6 +91,7 @@ TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisito constructor Create(const APosition: IPosition); destructor Destroy; override; procedure Accept(const AVisitor: ITemplateVisitor); virtual; abstract; + function Clone: IInterface; virtual; abstract; end; TAbstractStmt = class abstract(TAbstractBase, IStmt) @@ -97,40 +100,75 @@ TAbstractStmt = class abstract(TAbstractBase, IStmt) TDebugStmt = class(TAbstractStmt, IDebugStmt) private FStmt: IStmt; - procedure Accept(const AVisitor: ITemplateVisitor); override; function GetStmt: IStmt; public constructor Create(const AStmt: IStmt); + procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TEndStmt = class(TAbstractStmt, IEndStmt) + public + procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; + end; + + TExtendsStmt = class(TAbstractStmt, IExtendsStmt) private + FName: string; + FExpr: IExpr; + FContainer: ITemplate; // NOTE: FContainer is something that is resolved + function GetName: string; + function GetExpr: IExpr; + function GetContainer: ITemplate; + procedure SetContainer(const AContainer: ITemplate); + public + constructor Create(const APosition: IPosition; const AName: string; const AExpr: IExpr; const AContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; + end; + + TBlockStmt = class(TAbstractStmt, IBlockStmt) + private + FName: string; + FContainer: ITemplate; + function GetName: string; + function GetContainer: ITemplate; + procedure SetContainer(const AContainer: ITemplate); + public + constructor Create(const APosition: IPosition; const AName: string; const AContainer: ITemplate); + procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TElseStmt = class(TAbstractStmt, IElseStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TContinueStmt = class(TAbstractStmt, IContinueStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TBreakStmt = class(TAbstractStmt, IBreakStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TCommentStmt = class(TAbstractStmt, ICommentStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TElIfStmt = class(TAbstractStmt, IElIfStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TAbstractStmtWithExpr = class abstract(TAbstractStmt) @@ -144,11 +182,13 @@ TAbstractStmtWithExpr = class abstract(TAbstractStmt) TPrintStmt = class(TAbstractStmtWithExpr, IPrintStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TIncludeStmt = class(TAbstractStmtWithExpr, IIncludeStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; end; TRequireStmt = class(TAbstractStmt, IRequireStmt) @@ -156,6 +196,7 @@ TRequireStmt = class(TAbstractStmt, IRequireStmt) FExprList: IExprList; function GetExprList: IExprList; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AExprList: IExprList); end; @@ -168,8 +209,8 @@ TIfStmt = class(TAbstractStmt, IIfStmt) function GetCondition: IExpr; function GetTrueContainer: ITemplate; function GetFalseContainer: ITemplate; - procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const ACondition: IExpr; const ATrueContainer: ITemplate; const AFalseContainer: ITemplate); end; @@ -188,6 +229,7 @@ TProcessTemplateStmt = class(TAbstractStmtWithContainer, IProcessTemplateStmt) function GetAllowNewLine: boolean; procedure SetAllowNewLine(const AAllow: boolean); procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AContainer: ITemplate; const AAllowNewLine: boolean = true); end; @@ -197,6 +239,7 @@ TDefineTemplateStmt = class(TAbstractStmtWithContainer, IDefineTemplateStmt) FName: IExpr; function GetName: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); end; @@ -206,6 +249,7 @@ TWithStmt = class(TAbstractStmtWithContainer, IWithStmt) FExpr: IExpr; function GetExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AExpr: IExpr; const AContainer: ITemplate); end; @@ -234,6 +278,7 @@ TWhileStmt = class(TLoopStmt, IWhileStmt) procedure Accept(const AVisitor: ITemplateVisitor); override; function GetOffsetExpr: IExpr; function GetLimitExpr: IExpr; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const ACondition: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -251,6 +296,7 @@ TForInStmt = class(TLoopStmt, IForInStmt) function GetOffsetExpr: IExpr; function GetLimitExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const AExpr: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -268,6 +314,7 @@ TForRangeStmt = class(TLoopStmt, IForRangeStmt) function GetHighExpr: IExpr; function GetStepExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const ALowExpr: IExpr; const AHighExpr: IExpr; const AStep: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -277,6 +324,7 @@ TAssignStmt = class(TAbstractStmtWithExpr, IAssignStmt) FVariable: string; function GetVariable: string; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AExpr: IExpr); end; @@ -285,6 +333,7 @@ TCycleStmt = class(TAbstractStmt, ICycleStmt) private FExprList: IExprList; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AList: IExprList); function GetList: IExprList; @@ -297,10 +346,12 @@ TExprList = class(TAbstractBase, IExprList) procedure AddExpr(const AExpr: IExpr); function GetExprCount: integer; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Clone: IInterface; override; public end; TAbstractExpr = class abstract(TAbstractBase, IExpr) + function Clone: IInterface; override; end; TValueExpr = class(TAbstractExpr, IValueExpr) @@ -464,6 +515,9 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function ruleCycleStmt: IStmt; function ruleTemplateStmt: IStmt; + function ruleBlockStmt: IStmt; + function ruleExtendsStmt: IStmt; + function ruleExpression: IExpr; function ruleSimpleExpression: IExpr; function ruleTerm: IExpr; @@ -480,6 +534,30 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Parse(const AStream: TStream; const AManagedStream: boolean): ITemplate; end; +function CloneTemplate(const ATemplate: ITemplate): ITemplate; +var + LIntf: IInterface; +begin + LIntf := ATemplate.Clone; + supports(LIntf, ITemplate, result); +end; + +function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; +var + LIntf: IInterface; +begin + LIntf := AVisitorHost.Clone; + supports(LIntf, ITemplateVisitorHost, result); +end; + +function CloneStmt(const AStmt: IStmt): IStmt; +var + LIntf: IInterface; +begin + LIntf := AStmt.Clone; + supports(LIntf, IStmt, result); +end; + function CreateTemplateParser(AContext: ITemplateContext): ITemplateParser; begin exit(TTemplateParser.Create(AContext)); @@ -633,13 +711,16 @@ function TTemplateParser.ruleIgnoreNewline: IStmt; function TTemplateParser.ruleIncludeStmt: IStmt; var LSymbol: ITemplateSymbol; + LMatchSymbol: TTemplateSymbol; LIncludeExpr: IExpr; + LExtendsName: string; LScopeExpr: IExpr; LContainerTemplate: TTemplate; begin LSymbol := FLookahead; match(vsInclude); match(vsOpenRoundBracket); + LIncludeExpr := ruleExpression; if FLookahead.Token = vsComma then @@ -901,6 +982,10 @@ function TTemplateParser.ruleStmt: IStmt; result := ruleTemplateStmt; vsID: result := ruleIdStmt; + vsBlock: + result := ruleBlockStmt; + vsExtends: + result := ruleExtendsStmt; else result := ruleExprStmt; end; @@ -974,6 +1059,34 @@ function TTemplateParser.ruleAssignStmt(ASymbol: IExpr): IStmt; exit(TAssignStmt.Create(LSymbol.Position, (ASymbol as IVariableExpr).Variable, ruleExpression)); end; +function TTemplateParser.ruleBlockStmt: IStmt; +var + LId: string; + LSymbol: ITemplateSymbol; + LOptions: IPreserveValue; + LContainer: ITemplate; +begin + LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); + + LSymbol := FLookahead; + + match(vsBlock); + LId := matchValue(vsString); + match(vsEndScript); + + PushContainer; + LContainer := CurrentContainer; + + ruleStmts(LContainer, [vsEND]); + + match(vsEND); + match(vsEndScript); + + PopContainer; + exit(TBlockStmt.Create(LSymbol.Position, LId, LContainer)); + +end; + function TTemplateParser.ruleBreakStmt: IStmt; var LSymbol: ITemplateSymbol; @@ -1500,6 +1613,75 @@ function TTemplateParser.ruleExprStmt: IStmt; match(vsEndScript); end; +function TTemplateParser.ruleExtendsStmt: IStmt; +var + LName: string; + LExpr: IExpr; + LSymbol: ITemplateSymbol; + LOptions: IPreserveValue; + LContainer: ITemplate; + LBlockResolver: TBlockResolverVisitor; + LBlockReplacer: TBlockReplacerVisitor; + LBlockName: string; + LBlockNames: TArray; + LReplacementBlocks: TArray; + LTemplate: ITemplate; + LBlock: IBlockStmt; +begin + LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); + + LSymbol := FLookahead; + + match(vsExtends); + match(vsOpenRoundBracket); + LName := matchValue(vsString); + + if not FContext.TryGetTemplate(LName, LTemplate) then + RaiseErrorRes(LSymbol.Position, @STemplateNotFound, [LName]); + + if FLookahead.Token = vsComma then + begin + match(vsComma); + LExpr := ruleExpression; + end; + + match(vsCloseRoundBracket); + + match(vsEndScript); + + PushContainer; + LContainer := CurrentContainer; + + ruleStmts(LContainer, [vsEND]); + + match(vsEND); + match(vsEndScript); + + PopContainer; + + LBlockResolver := TBlockResolverVisitor.Create(); + try + AcceptVisitor(LContainer, LBlockResolver); + LBlockNames := LBlockResolver.GetBlockNames; + LBlockReplacer := TBlockReplacerVisitor.Create(); + try + for LBlockName in LBlockNames do + begin + LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); + for LBlock in LReplacementBlocks do + begin + LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); + end; + end; + finally + // LBlockReplacer.Free; + end; + finally + // LBlockResolver.Free; + end; + exit(TExtendsStmt.Create(LSymbol.Position, LName, LExpr, LTemplate)); +end; + function TTemplateParser.CurrentContainer: ITemplate; begin if FContainerStack.Count <> 0 then @@ -1630,6 +1812,11 @@ procedure TExprList.AddExpr(const AExpr: IExpr); FExprs[LOffset] := AExpr; end; +function TExprList.Clone: IInterface; +begin + exit(self); +end; + function TExprList.GetExpr(const AOffset: integer): IExpr; begin exit(FExprs[AOffset]); @@ -1702,6 +1889,11 @@ procedure TIfStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TIfStmt.Clone: IInterface; +begin + exit(TIfStmt.Create(FPosition, FCondition, CloneTemplate(FTrueContainer), CloneTemplate(FFalseContainer))); +end; + constructor TIfStmt.Create(const APosition: IPosition; const ACondition: IExpr; const ATrueContainer: ITemplate; const AFalseContainer: ITemplate); begin inherited Create(APosition); @@ -1769,6 +1961,11 @@ procedure TPrintStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TPrintStmt.Clone: IInterface; +begin + exit(TPrintStmt.Create(FPosition, FExpr)); +end; + { TForInStmt } procedure TForInStmt.Accept(const AVisitor: ITemplateVisitor); @@ -1776,6 +1973,11 @@ procedure TForInStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TForInStmt.Clone: IInterface; +begin + exit(TForInStmt.Create(FPosition, FVariable, FForOp, FExpr, FOffsetExpr, FLimitExpr, FContainer, FOnFirst, FOnLast, FOnEmpty, FBetweenItem)); +end; + constructor TForInStmt.Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const AExpr: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -1818,6 +2020,11 @@ procedure TForRangeStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TForRangeStmt.Clone: IInterface; +begin + exit(TForRangeStmt.Create(FPosition, FVariable, FForOp, FLowExpr, FHighExpr, FStepExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); +end; + constructor TForRangeStmt.Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const ALowExpr: IExpr; const AHighExpr: IExpr; const AStep: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -1860,6 +2067,11 @@ procedure TAssignStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TAssignStmt.Clone: IInterface; +begin + exit(TAssignStmt.Create(FPosition, FVariable, FExpr)); +end; + constructor TAssignStmt.Create(const APosition: IPosition; const AVariable: string; const AExpr: IExpr); begin inherited Create(APosition, AExpr); @@ -1892,6 +2104,19 @@ procedure TTemplate.Add(const AItem: ITemplateVisitorHost); FArray[LOffset] := AItem; end; +function TTemplate.Clone: IInterface; +var + i: ITemplateVisitorHost; + LTemplate: TTemplate; +begin + LTemplate := TTemplate.Create; + result := LTemplate; + for i in FArray do + begin + LTemplate.Add(CloneVisitorHost(i)); + end; +end; + function TTemplate.GetCount: integer; begin exit(length(FArray)); @@ -1917,6 +2142,11 @@ procedure TWhileStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TWhileStmt.Clone: IInterface; +begin + exit(TWhileStmt.Create(FPosition, FCondition, FOffsetExpr, FLimitExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); +end; + constructor TWhileStmt.Create(const APosition: IPosition; const ACondition: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -1947,6 +2177,11 @@ procedure TContinueStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TContinueStmt.Clone: IInterface; +begin + exit(TContinueStmt.Create(FPosition)); +end; + { TBreakStmt } procedure TBreakStmt.Accept(const AVisitor: ITemplateVisitor); @@ -1954,6 +2189,11 @@ procedure TBreakStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TBreakStmt.Clone: IInterface; +begin + exit(TBreakStmt.Create(FPosition)); +end; + { TEndStmt } procedure TEndStmt.Accept(const AVisitor: ITemplateVisitor); @@ -1961,6 +2201,11 @@ procedure TEndStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TEndStmt.Clone: IInterface; +begin + exit(TEndStmt.Create(FPosition)); +end; + { TVariableDerefExpr } procedure TVariableDerefExpr.Accept(const AVisitor: ITemplateVisitor); @@ -1992,6 +2237,11 @@ procedure TIncludeStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TIncludeStmt.Clone: IInterface; +begin + exit(TIncludeStmt.Create(FPosition, FExpr)); +end; + { TElseStmt } procedure TElseStmt.Accept(const AVisitor: ITemplateVisitor); @@ -1999,6 +2249,11 @@ procedure TElseStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TElseStmt.Clone: IInterface; +begin + exit(TElseStmt.Create(FPosition)); +end; + { TElIfStmt } procedure TElIfStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2006,6 +2261,11 @@ procedure TElIfStmt.Accept(const AVisitor: ITemplateVisitor); // AVisitor.Visit(self); // this is on purpose end; +function TElIfStmt.Clone: IInterface; +begin + exit(TElIfStmt.Create(FPosition)); +end; + { TCommentStmt } procedure TCommentStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2013,6 +2273,11 @@ procedure TCommentStmt.Accept(const AVisitor: ITemplateVisitor); // AVisitor.Visit(self); // this is on purpose end; +function TCommentStmt.Clone: IInterface; +begin + exit(TCommentStmt.Create(FPosition)); +end; + { TAbstractBase } constructor TAbstractBase.Create(const APosition: IPosition); @@ -2079,6 +2344,11 @@ procedure TProcessTemplateStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TProcessTemplateStmt.Clone: IInterface; +begin + exit(TProcessTemplateStmt.Create(FPosition, CloneTemplate(FContainer), FAllowNewline)); +end; + constructor TProcessTemplateStmt.Create(const APosition: IPosition; const AContainer: ITemplate; const AAllowNewLine: boolean); begin inherited Create(APosition, AContainer); @@ -2102,6 +2372,11 @@ procedure TDefineTemplateStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TDefineTemplateStmt.Clone: IInterface; +begin + exit(TDefineTemplateStmt.Create(FPosition, FName, CloneTemplate(FContainer))); +end; + constructor TDefineTemplateStmt.Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); begin inherited Create(APosition, AContainer); @@ -2120,6 +2395,11 @@ procedure TWithStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TWithStmt.Clone: IInterface; +begin + exit(TWithStmt.Create(FPosition, FExpr, CloneTemplate(FContainer))); +end; + constructor TWithStmt.Create(const APosition: IPosition; const AExpr: IExpr; const AContainer: ITemplate); begin inherited Create(APosition, AContainer); @@ -2192,6 +2472,11 @@ procedure TRequireStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TRequireStmt.Clone: IInterface; +begin + exit(TRequireStmt.Create(FPosition, FExprList)); +end; + constructor TRequireStmt.Create(const APosition: IPosition; const AExprList: IExprList); begin inherited Create(APosition); @@ -2262,6 +2547,11 @@ procedure TCycleStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TCycleStmt.Clone: IInterface; +begin + exit(TCycleStmt.Create(FPosition, FExprList)); +end; + constructor TCycleStmt.Create(const APosition: IPosition; const AList: IExprList); begin inherited Create(APosition); @@ -2280,6 +2570,11 @@ procedure TDebugStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +function TDebugStmt.Clone: IInterface; +begin + exit(TDebugStmt.Create(CloneStmt(FStmt))); +end; + constructor TDebugStmt.Create(const AStmt: IStmt); begin FStmt := AStmt; @@ -2321,6 +2616,87 @@ function TLoopStmt.GetOnFirstContainer: ITemplate; exit(FOnFirst); end; +{ TBlockStmt } + +procedure TBlockStmt.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +function TBlockStmt.Clone: IInterface; +begin + exit(TBlockStmt.Create(FPosition, FName, FContainer)); +end; + +constructor TBlockStmt.Create(const APosition: IPosition; const AName: string; const AContainer: ITemplate); +begin + inherited Create(APosition); + FName := AName; + FContainer := AContainer; +end; + +function TBlockStmt.GetContainer: ITemplate; +begin + exit(FContainer); +end; + +function TBlockStmt.GetName: string; +begin + exit(FName); +end; + +procedure TBlockStmt.SetContainer(const AContainer: ITemplate); +begin + FContainer := AContainer; +end; + +{ TExtendsStmt } + +procedure TExtendsStmt.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +function TExtendsStmt.Clone: IInterface; +begin + exit(TExtendsStmt.Create(FPosition, FName, FExpr, CloneTemplate(FContainer))); +end; + +constructor TExtendsStmt.Create(const APosition: IPosition; const AName: string; const AExpr: IExpr; const AContainer: ITemplate); +begin + inherited Create(APosition); + FName := AName; + FExpr := AExpr; + FContainer := CloneTemplate(AContainer); +end; + +function TExtendsStmt.GetContainer: ITemplate; +begin + exit(FContainer); +end; + +function TExtendsStmt.GetExpr: IExpr; +begin + exit(FExpr); +end; + +function TExtendsStmt.GetName: string; +begin + exit(FName); +end; + +procedure TExtendsStmt.SetContainer(const AContainer: ITemplate); +begin + FContainer := AContainer; +end; + +{ TAbstractExpr } + +function TAbstractExpr.Clone: IInterface; +begin + exit(self); +end; + initialization initOps; diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 2d282d3..10d41ea 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -84,6 +84,10 @@ TPrettyPrintTemplateVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; procedure Visit(const AStmt: IWithStmt); overload; override; procedure Visit(const AStmt: ICycleStmt); overload; override; + procedure Visit(const AStmt: IDebugStmt); overload; override; + + procedure Visit(const AStmt: IBlockStmt); overload; override; + procedure Visit(const AStmt: IExtendsStmt); overload; override; end; @@ -545,6 +549,34 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: ICycleStmt); writeln('%>'); end; +procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDebugStmt); +begin + // just proxy through + AcceptVisitor(AStmt.Stmt, self); +end; + +procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IBlockStmt); +begin + tab(); + write('<% block ''' + AStmt.Name + '''%>'); + delta(4); + AcceptVisitor(AStmt.Container, self); + delta(-4); + tab(); + writeln('<% end %>'); +end; + +procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IExtendsStmt); +begin + tab(); + write('<% extends ''' + AStmt.Name + '''%>'); + delta(4); + AcceptVisitor(AStmt.Container, self); + delta(-4); + tab(); + writeln('<% end %>'); +end; + initialization GUnaryStrings[uoMinus] := '-'; diff --git a/src/Sempare.Template.VariableExtraction.pas b/src/Sempare.Template.VariableExtraction.pas index 69a0b4b..a8dc7bf 100644 --- a/src/Sempare.Template.VariableExtraction.pas +++ b/src/Sempare.Template.VariableExtraction.pas @@ -84,6 +84,12 @@ TTemplateReferenceExtractionVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; procedure Visit(const AStmt: IWithStmt); overload; override; + procedure Visit(const AStmt: ICycleStmt); overload; override; + procedure Visit(const AStmt: IDebugStmt); overload; override; + + procedure Visit(const AStmt: IBlockStmt); overload; override; + procedure Visit(const AStmt: IExtendsStmt); overload; override; + property Variables: TArray read GetVariables; property Functions: TArray read GetFunctions; end; @@ -267,4 +273,29 @@ procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IVariableExpr); FVariables.Add(AExpr.Variable); end; +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: ICycleStmt); +var + LIdx: integer; +begin + for LIdx := 0 to AStmt.List.Count - 1 do + begin + AcceptVisitor(AStmt.List.Expr[LIdx], self); + end; +end; + +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IDebugStmt); +begin + AcceptVisitor(AStmt.Stmt, self); +end; + +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IBlockStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IExtendsStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + end. diff --git a/src/Sempare.Template.Visitor.pas b/src/Sempare.Template.Visitor.pas index 15691bf..b12d745 100644 --- a/src/Sempare.Template.Visitor.pas +++ b/src/Sempare.Template.Visitor.pas @@ -77,6 +77,8 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AStmt: IWithStmt); overload; virtual; procedure Visit(const AStmt: ICycleStmt); overload; virtual; procedure Visit(const AStmt: IDebugStmt); overload; virtual; + procedure Visit(const AStmt: IBlockStmt); overload; virtual; + procedure Visit(const AStmt: IExtendsStmt); overload; virtual; end; implementation @@ -253,4 +255,14 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IDebugStmt); end; +procedure TBaseTemplateVisitor.Visit(const AStmt: IBlockStmt); +begin + +end; + +procedure TBaseTemplateVisitor.Visit(const AStmt: IExtendsStmt); +begin + +end; + end. diff --git a/tests/Sempare.Template.TestIf.pas b/tests/Sempare.Template.TestIf.pas index f1bc896..868214b 100644 --- a/tests/Sempare.Template.TestIf.pas +++ b/tests/Sempare.Template.TestIf.pas @@ -34,6 +34,8 @@ interface +{$I 'Sempare.Template.Compiler.inc'} + uses DUnitX.TestFramework; diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index 5736023..d53e6bc 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -59,6 +59,12 @@ TTestTemplateInclude = class [Test] procedure TestSubTemplate; + + [Test] + procedure TestExtends; + + [Test] + procedure TestExtendsBlock; end; implementation @@ -266,6 +272,57 @@ TTemplate = record end; +procedure TTestTemplateInclude.TestExtends; +var + LTpl: ITemplate; + LCtx: ITemplateContext; +begin + LCtx := Template.Context(); + LCtx.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate + begin + if AName = 'showmember' then + begin + exit(Template.parse(AContext, '<% block ''content'' %>parent<% end %>')); + end + else + exit(nil); + end; + + LTpl := Template.parse(LCtx, // + '<% extends (''showmember'') %>' + // + '<% end %> ' + // + '<% extends (''showmember'') %>' + // + '<% end %>' // + ); + Assert.AreEqual('parent parent', Template.Eval(LTpl)); +end; + +procedure TTestTemplateInclude.TestExtendsBlock; +var + LTpl: ITemplate; + LCtx: ITemplateContext; +begin + LCtx := Template.Context(); + LCtx.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate + begin + if AName = 'showmember' then + begin + exit(Template.parse(AContext, '<% block ''content'' %>parent<% end %>')); + end + else + exit(nil); + end; + LTpl := Template.parse(LCtx, // + '<% extends (''showmember'') %>' + // + '<% block ''content'' %>child<% end %>' + // + '<% end %> ' + // + '<% extends (''showmember'') %>' + // + '<% block ''content'' %>child2<% end %>' + // + '<% end %>' // + ); + Assert.AreEqual('child child2', Template.Eval(LTpl)); +end; + initialization TDUnitX.RegisterTestFixture(TTestTemplateInclude); From 6d1e539c0239af910661bd2e9c8b4e985cd9af0e Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 26 Mar 2023 23:57:40 +0100 Subject: [PATCH 003/138] Add missing BlockReplacer/BlockResolver --- src/Sempare.Template.BlockReplacer.pas | 146 ++++++++++++++++++++++ src/Sempare.Template.BlockResolver.pas | 162 +++++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 src/Sempare.Template.BlockReplacer.pas create mode 100644 src/Sempare.Template.BlockResolver.pas diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas new file mode 100644 index 0000000..d56b277 --- /dev/null +++ b/src/Sempare.Template.BlockReplacer.pas @@ -0,0 +1,146 @@ +(*%************************************************************************************************* + * ___ * + * / __| ___ _ __ _ __ __ _ _ _ ___ * + * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * + * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * + * |_| * + **************************************************************************************************** + * * + * Sempare Template Engine * + * * + * * + * https://github.com/sempare/sempare-delphi-template-engine * + **************************************************************************************************** + * * + * Copyright (c) 2019-2023 Sempare Limited * + * * + * Contact: info@sempare.ltd * + * * + * Licensed under the GPL Version 3.0 or the Sempare Commercial License * + * You may not use this file except in compliance with one of these Licenses. * + * You may obtain a copy of the Licenses at * + * * + * https://www.gnu.org/licenses/gpl-3.0.en.html * + * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the Licenses is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + *************************************************************************************************%*) +unit Sempare.Template.BlockReplacer; + +interface + +uses + Sempare.Template.AST, + Sempare.Template.Common, + Sempare.Template.Visitor; + +type + + TBlockReplacerVisitor = class(TBaseTemplateVisitor) + private + FBlockName: string; + FReplacementBlock: ITemplate; + public + + procedure Visit(const AStmt: IIfStmt); overload; override; + procedure Visit(const AStmt: IWhileStmt); overload; override; + procedure Visit(const AStmt: IForInStmt); overload; override; + procedure Visit(const AStmt: IForRangeStmt); overload; override; + + procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; + procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; + procedure Visit(const AStmt: IWithStmt); overload; override; + + procedure Visit(const AStmt: IDebugStmt); overload; override; + + procedure Visit(const AStmt: IBlockStmt); overload; override; + procedure Visit(const AStmt: IExtendsStmt); overload; override; + + procedure Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); + + end; + +implementation + +uses + System.SysUtils; + +{ TBlockReplacerVisitor } + +procedure TBlockReplacerVisitor.Visit(const AStmt: IForRangeStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IProcessTemplateStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IDefineTemplateStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IWithStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IForInStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IIfStmt); +begin + AcceptVisitor(AStmt.TrueContainer, self); + if AStmt.FalseContainer <> nil then + begin + AcceptVisitor(AStmt.FalseContainer, self); + end; +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IWhileStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IDebugStmt); +begin + AcceptVisitor(AStmt.Stmt, self); +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IBlockStmt); +begin + if (AStmt.Name = FBlockName) and assigned(AStmt.Container) then + begin + AStmt.Container := FReplacementBlock; + end; +end; + +procedure TBlockReplacerVisitor.Visit(const AStmt: IExtendsStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); +var + LVisitor: ITemplateVisitor; +begin + if not supports(self, ITemplateVisitor, LVisitor) then + exit; + if not assigned(ABlock) then + exit; + FBlockName := ABlockName; + FReplacementBlock := ABlock; + AcceptVisitor(ATemplate, LVisitor); + FReplacementBlock := nil; +end; + +end. diff --git a/src/Sempare.Template.BlockResolver.pas b/src/Sempare.Template.BlockResolver.pas new file mode 100644 index 0000000..65ceb7c --- /dev/null +++ b/src/Sempare.Template.BlockResolver.pas @@ -0,0 +1,162 @@ +(*%************************************************************************************************* + * ___ * + * / __| ___ _ __ _ __ __ _ _ _ ___ * + * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * + * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * + * |_| * + **************************************************************************************************** + * * + * Sempare Template Engine * + * * + * * + * https://github.com/sempare/sempare-delphi-template-engine * + **************************************************************************************************** + * * + * Copyright (c) 2019-2023 Sempare Limited * + * * + * Contact: info@sempare.ltd * + * * + * Licensed under the GPL Version 3.0 or the Sempare Commercial License * + * You may not use this file except in compliance with one of these Licenses. * + * You may obtain a copy of the Licenses at * + * * + * https://www.gnu.org/licenses/gpl-3.0.en.html * + * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the Licenses is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + *************************************************************************************************%*) +unit Sempare.Template.BlockResolver; + +interface + +uses + System.Generics.Collections, + Sempare.Template.AST, + Sempare.Template.Common, + Sempare.Template.Visitor; + +type + + TBlockResolverVisitor = class(TBaseTemplateVisitor) + private + FBlocks: TObjectDictionary>; + public + constructor Create; + destructor Destroy; override; + + function GetBlockNames: TArray; + function GetBlocks(const AName: string): TArray; + + procedure Visit(const AStmt: IIfStmt); overload; override; + procedure Visit(const AStmt: IWhileStmt); overload; override; + procedure Visit(const AStmt: IForInStmt); overload; override; + procedure Visit(const AStmt: IForRangeStmt); overload; override; + + procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; + procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; + procedure Visit(const AStmt: IWithStmt); overload; override; + + procedure Visit(const AStmt: IDebugStmt); overload; override; + + procedure Visit(const AStmt: IBlockStmt); overload; override; + procedure Visit(const AStmt: IExtendsStmt); overload; override; + + end; + +implementation + +{ TBlockResolverVisitor } + +procedure TBlockResolverVisitor.Visit(const AStmt: IForRangeStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IProcessTemplateStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IDefineTemplateStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IWithStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IForInStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IIfStmt); +begin + AcceptVisitor(AStmt.TrueContainer, self); + if AStmt.FalseContainer <> nil then + begin + AcceptVisitor(AStmt.FalseContainer, self); + end; +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IWhileStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IDebugStmt); +begin + AcceptVisitor(AStmt.Stmt, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IBlockStmt); +var + LList: TList; +begin + if not FBlocks.TryGetValue(AStmt.Name, LList) then + begin + LList := TList.Create; + FBlocks.Add(AStmt.Name, LList); + end; + LList.Add(AStmt); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); +begin + AcceptVisitor(AStmt.Container, self); +end; + +constructor TBlockResolverVisitor.Create; +begin + FBlocks := TObjectDictionary < string, TList < IBlockStmt >>.Create([doOwnsValues]); +end; + +destructor TBlockResolverVisitor.Destroy; +begin + FBlocks.Free; + inherited; +end; + +function TBlockResolverVisitor.GetBlockNames: TArray; +begin + exit(FBlocks.Keys.ToArray); +end; + +function TBlockResolverVisitor.GetBlocks(const AName: string): TArray; +var + LList: TList; +begin + if FBlocks.TryGetValue(AName, LList) then + exit(LList.ToArray) + else + exit(nil); +end; + +end. From 0cdf8ff3bc3420eec24b3e4a2ff30a8656a43b85 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 27 Mar 2023 10:36:46 +0100 Subject: [PATCH 004/138] Fixed AV. Issue due to mixing Free + interface concerns. --- src/Sempare.Template.BlockReplacer.pas | 11 +++++++-- src/Sempare.Template.BlockResolver.pas | 24 +++++++++++-------- src/Sempare.Template.Parser.pas | 32 +++++++++++--------------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index d56b277..515b52f 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -40,8 +40,12 @@ interface Sempare.Template.Visitor; type + IBlockReplacerVisitor = interface(ITemplateVisitor) + ['{81560D41-C5D1-49DF-9A26-98F2D7951E3A}'] + procedure Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); + end; - TBlockReplacerVisitor = class(TBaseTemplateVisitor) + TBlockReplacerVisitor = class(TBaseTemplateVisitor, IBlockReplacerVisitor) private FBlockName: string; FReplacementBlock: ITemplate; @@ -118,7 +122,10 @@ procedure TBlockReplacerVisitor.Visit(const AStmt: IDebugStmt); procedure TBlockReplacerVisitor.Visit(const AStmt: IBlockStmt); begin - if (AStmt.Name = FBlockName) and assigned(AStmt.Container) then + if not assigned(AStmt.Container) then + exit; + AcceptVisitor(AStmt.Container, self); + if AStmt.Name = FBlockName then begin AStmt.Container := FReplacementBlock; end; diff --git a/src/Sempare.Template.BlockResolver.pas b/src/Sempare.Template.BlockResolver.pas index 65ceb7c..8b55f46 100644 --- a/src/Sempare.Template.BlockResolver.pas +++ b/src/Sempare.Template.BlockResolver.pas @@ -41,10 +41,15 @@ interface Sempare.Template.Visitor; type + IBlockResolverVisitor = interface(ITemplateVisitor) + ['{623A7C4A-3592-46BD-A3C5-FE354E0E67C0}'] + function GetBlockNames: TArray; + function GetBlocks(const AName: string): TArray; + end; - TBlockResolverVisitor = class(TBaseTemplateVisitor) + TBlockResolverVisitor = class(TBaseTemplateVisitor, IBlockResolverVisitor) private - FBlocks: TObjectDictionary>; + FBlocks: TDictionary>; public constructor Create; destructor Destroy; override; @@ -118,14 +123,14 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IDebugStmt); procedure TBlockResolverVisitor.Visit(const AStmt: IBlockStmt); var - LList: TList; + LList: TArray; begin if not FBlocks.TryGetValue(AStmt.Name, LList) then begin - LList := TList.Create; - FBlocks.Add(AStmt.Name, LList); + LList := nil; end; - LList.Add(AStmt); + insert(AStmt, LList, length(LList)); + FBlocks.AddOrSetValue(AStmt.Name, LList); end; procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); @@ -135,11 +140,12 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); constructor TBlockResolverVisitor.Create; begin - FBlocks := TObjectDictionary < string, TList < IBlockStmt >>.Create([doOwnsValues]); + FBlocks := TDictionary < string, TArray < IBlockStmt >>.Create(); end; destructor TBlockResolverVisitor.Destroy; begin + FBlocks.Clear; FBlocks.Free; inherited; end; @@ -151,10 +157,10 @@ function TBlockResolverVisitor.GetBlockNames: TArray; function TBlockResolverVisitor.GetBlocks(const AName: string): TArray; var - LList: TList; + LList: TArray; begin if FBlocks.TryGetValue(AName, LList) then - exit(LList.ToArray) + exit(LList) else exit(nil); end; diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 7dfba6d..9eab813 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -1620,8 +1620,8 @@ function TTemplateParser.ruleExtendsStmt: IStmt; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LBlockResolver: TBlockResolverVisitor; - LBlockReplacer: TBlockReplacerVisitor; + LBlockResolver: IBlockResolverVisitor; + LBlockReplacer: IBlockReplacerVisitor; LBlockName: string; LBlockNames: TArray; LReplacementBlocks: TArray; @@ -1660,24 +1660,18 @@ function TTemplateParser.ruleExtendsStmt: IStmt; PopContainer; LBlockResolver := TBlockResolverVisitor.Create(); - try - AcceptVisitor(LContainer, LBlockResolver); - LBlockNames := LBlockResolver.GetBlockNames; - LBlockReplacer := TBlockReplacerVisitor.Create(); - try - for LBlockName in LBlockNames do - begin - LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); - for LBlock in LReplacementBlocks do - begin - LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); - end; - end; - finally - // LBlockReplacer.Free; + AcceptVisitor(LContainer, LBlockResolver); + LBlockNames := LBlockResolver.GetBlockNames; + + LBlockReplacer := TBlockReplacerVisitor.Create(); + + for LBlockName in LBlockNames do + begin + LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); + for LBlock in LReplacementBlocks do + begin + LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); end; - finally - // LBlockResolver.Free; end; exit(TExtendsStmt.Create(LSymbol.Position, LName, LExpr, LTemplate)); end; From 6b28ca11df009e0bc8515fd065db08fbedc36dfc Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 27 Mar 2023 15:54:55 +0100 Subject: [PATCH 005/138] work in progress --- src/Sempare.Template.AST.pas | 23 ++++++-- src/Sempare.Template.BlockReplacer.pas | 8 ++- src/Sempare.Template.BlockResolver.pas | 13 +++-- src/Sempare.Template.Evaluate.pas | 78 ++++++++++++++++++++++++- src/Sempare.Template.Parser.pas | 81 ++++++++++++-------------- src/Sempare.Template.PrettyPrint.pas | 8 ++- tests/Sempare.Template.TestInclude.pas | 4 +- 7 files changed, 156 insertions(+), 59 deletions(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index cad978f..3a03bb3 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -165,6 +165,7 @@ ETemplate = class(Exception); end; ITemplateVisitor = interface; + IEvaluationTemplateVisitor = interface; IPositional = interface ['{DFA45EC1-7F39-4FB2-9894-8BD8D4ABA975}'] @@ -212,21 +213,25 @@ ETemplate = class(Exception); IExtendsStmt = interface(IStmt) ['{220D7E83-280D-454B-BA60-622C97EBE131}'] - function GetName: string; + function GetName: IExpr; + function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; + function GetBlockContainer: ITemplate; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); function GetExpr: IExpr; - property Name: string read GetName; + property Name: IExpr read GetName; property Expr: IExpr read GetExpr; property Container: ITemplate read GetContainer write SetContainer; + property BlockContainer: ITemplate read GetBlockContainer; end; IBlockStmt = interface(IStmt) ['{EBAC38C4-9790-4D7C-844C-BE7C94E7C822}'] - function GetName: string; + function GetName: IExpr; + function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); - property Name: string read GetName; + property Name: IExpr read GetName; property Container: ITemplate read GetContainer write SetContainer; end; @@ -514,6 +519,16 @@ ETemplate = class(Exception); procedure Visit(const AStmt: IExtendsStmt); overload; end; + IEvaluationTemplateVisitor = interface(ITemplateVisitor) + ['{D7993669-463E-4DBD-ACA2-76A7A6FF059A}'] + function EvalExpr(const AExpr: IExpr): TValue; + function EvalExprAsString(const AExpr: IExpr): string; + function EvalExprAsInt(const AExpr: IExpr): int64; + function EvalExprAsNum(const AExpr: IExpr): extended; + function EvalExprAsBoolean(const AExpr: IExpr): boolean; + procedure VisitStmt(const AStmt: IStmt); + end; + implementation end. diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index 515b52f..a0ba77b 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -49,8 +49,9 @@ TBlockReplacerVisitor = class(TBaseTemplateVisitor, IBlockReplacerVisitor) private FBlockName: string; FReplacementBlock: ITemplate; + FEvalVisitor: IEvaluationTemplateVisitor; public - + constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); procedure Visit(const AStmt: IIfStmt); overload; override; procedure Visit(const AStmt: IWhileStmt); overload; override; procedure Visit(const AStmt: IForInStmt); overload; override; @@ -136,6 +137,11 @@ procedure TBlockReplacerVisitor.Visit(const AStmt: IExtendsStmt); AcceptVisitor(AStmt.Container, self); end; +constructor TBlockReplacerVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor); +begin + FEvalVisitor := AEvalVisitor; +end; + procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); var LVisitor: ITemplateVisitor; diff --git a/src/Sempare.Template.BlockResolver.pas b/src/Sempare.Template.BlockResolver.pas index 8b55f46..ac4e695 100644 --- a/src/Sempare.Template.BlockResolver.pas +++ b/src/Sempare.Template.BlockResolver.pas @@ -38,6 +38,7 @@ interface System.Generics.Collections, Sempare.Template.AST, Sempare.Template.Common, + Sempare.Template.Evaluate, Sempare.Template.Visitor; type @@ -50,8 +51,9 @@ interface TBlockResolverVisitor = class(TBaseTemplateVisitor, IBlockResolverVisitor) private FBlocks: TDictionary>; + FEvalVisitor: IEvaluationTemplateVisitor; public - constructor Create; + constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); destructor Destroy; override; function GetBlockNames: TArray; @@ -124,13 +126,15 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IDebugStmt); procedure TBlockResolverVisitor.Visit(const AStmt: IBlockStmt); var LList: TArray; + LName: string; begin - if not FBlocks.TryGetValue(AStmt.Name, LList) then + LName := FEvalVisitor.EvalExprAsString(AStmt.Name); + if not FBlocks.TryGetValue(LName, LList) then begin LList := nil; end; insert(AStmt, LList, length(LList)); - FBlocks.AddOrSetValue(AStmt.Name, LList); + FBlocks.AddOrSetValue(LName, LList); end; procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); @@ -138,8 +142,9 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); AcceptVisitor(AStmt.Container, self); end; -constructor TBlockResolverVisitor.Create; +constructor TBlockResolverVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor); begin + FEvalVisitor := AEvalVisitor; FBlocks := TDictionary < string, TArray < IBlockStmt >>.Create(); end; diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 8c2014b..9b2655b 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -77,7 +77,7 @@ TNewLineStreamWriter = class(TStreamWriter) property IgnoreNewLine: boolean read FIgnoreNewline write FIgnoreNewline; end; - TEvaluationTemplateVisitor = class(TBaseTemplateVisitor) + TEvaluationTemplateVisitor = class(TBaseTemplateVisitor, IEvaluationTemplateVisitor) private FStopWatch: TStopWatch; FStackFrames: TObjectStack; @@ -96,7 +96,13 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor) function ExprListArgs(const AExprList: IExprList): TArray; function Invoke(const AFuncCall: IFunctionCallExpr; const AArgs: TArray; out AHasResult: boolean): TValue; overload; function Invoke(const AExpr: IMethodCallExpr; const AObject: TValue; const AArgs: TArray; out AHasResult: boolean): TValue; overload; - + function EvalExpr(const AExpr: IExpr): TValue; + function EvalExprAsString(const AExpr: IExpr): string; + function EvalExprAsInt(const AExpr: IExpr): int64; + function EvalExprAsNum(const AExpr: IExpr): extended; + function EvalExprAsBoolean(const AExpr: IExpr): boolean; + procedure VisitStmt(const AStmt: IStmt); + procedure VisitContainer(const AContainer: ITemplate); public constructor Create(const AContext: ITemplateContext; const AValue: TValue; const AStream: TStream); overload; constructor Create(const AContext: ITemplateContext; const AStackFrame: TStackFrame; const AStream: TStream); overload; @@ -143,6 +149,8 @@ implementation uses Data.DB, System.TypInfo, // needed for XE6 and below to access the TTypeKind variables + Sempare.Template.BlockResolver, + Sempare.Template.BlockReplacer, Sempare.Template.ResourceStrings, Sempare.Template.Rtti, Sempare.Template.Util; @@ -709,6 +717,32 @@ function TEvaluationTemplateVisitor.EncodeVariable(const AValue: TValue): TValue exit(FContext.VariableEncoder(AsString(AValue, FContext))); end; +function TEvaluationTemplateVisitor.EvalExprAsBoolean(const AExpr: IExpr): boolean; +begin + exit(AsBoolean(EvalExpr(AExpr))); +end; + +function TEvaluationTemplateVisitor.EvalExpr(const AExpr: IExpr): TValue; +begin + AcceptVisitor(AExpr, self); + exit(FEvalStack.pop); +end; + +function TEvaluationTemplateVisitor.EvalExprAsInt(const AExpr: IExpr): int64; +begin + exit(AsInt(EvalExpr(AExpr), FContext)); +end; + +function TEvaluationTemplateVisitor.EvalExprAsNum(const AExpr: IExpr): extended; +begin + exit(AsNum(EvalExpr(AExpr), FContext)); +end; + +function TEvaluationTemplateVisitor.EvalExprAsString(const AExpr: IExpr): string; +begin + exit(AsString(EvalExpr(AExpr), FContext)); +end; + function TEvaluationTemplateVisitor.ExprListArgs(const AExprList: IExprList): TArray; var LIdx: integer; @@ -1020,6 +1054,11 @@ function TEvaluationTemplateVisitor.Invoke(const AExpr: IMethodCallExpr; const A exit(AExpr.RttiMethod.Invoke(AObject, AArgs)); end; +procedure TEvaluationTemplateVisitor.VisitStmt(const AStmt: IStmt); +begin + AcceptVisitor(AStmt, self); +end; + procedure TEvaluationTemplateVisitor.Visit(const AExpr: IMethodCallExpr); var LObj: TValue; @@ -1165,9 +1204,44 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IBlockStmt); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); +var + LName: string; + LSymbol: ITemplateSymbol; + LBlockResolver: IBlockResolverVisitor; + LBlockReplacer: IBlockReplacerVisitor; + LBlockName: string; + LBlockNames: TArray; + LReplacementBlocks: TArray; + LTemplate: ITemplate; + LBlock: IBlockStmt; begin if assigned(AStmt.Container) then + begin + if not FContext.TryGetTemplate(LName, LTemplate) then + RaiseErrorRes(LSymbol.Position, @STemplateNotFound, [LName]); + AcceptVisitor(AStmt.Container, self); + + LBlockResolver := TBlockResolverVisitor.Create(self); + AcceptVisitor(AStmt.BlockContainer, LBlockResolver); + + LBlockNames := LBlockResolver.GetBlockNames; + + LBlockReplacer := TBlockReplacerVisitor.Create(self); + for LBlockName in LBlockNames do + begin + LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); + for LBlock in LReplacementBlocks do + begin + LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); + end; + end; + end; +end; + +procedure TEvaluationTemplateVisitor.VisitContainer(const AContainer: ITemplate); +begin + AcceptVisitor(AContainer, self); end; { TNewLineStreamWriter } diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 9eab813..46edf66 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -115,28 +115,32 @@ TEndStmt = class(TAbstractStmt, IEndStmt) TExtendsStmt = class(TAbstractStmt, IExtendsStmt) private - FName: string; + FName: IExpr; FExpr: IExpr; + FBlockContainer: ITemplate; // NOTE: FContainer is something that is resolved FContainer: ITemplate; // NOTE: FContainer is something that is resolved - function GetName: string; + function GetName: IExpr; function GetExpr: IExpr; + function GetBlockContainer: ITemplate; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); + function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; public - constructor Create(const APosition: IPosition; const AName: string; const AExpr: IExpr; const AContainer: ITemplate); + constructor Create(const APosition: IPosition; const AName, AExpr: IExpr; const ABlockContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; function Clone: IInterface; override; end; TBlockStmt = class(TAbstractStmt, IBlockStmt) private - FName: string; + FName: IExpr; FContainer: ITemplate; - function GetName: string; + function GetName: IExpr; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); + function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; public - constructor Create(const APosition: IPosition; const AName: string; const AContainer: ITemplate); + constructor Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; function Clone: IInterface; override; end; @@ -711,9 +715,7 @@ function TTemplateParser.ruleIgnoreNewline: IStmt; function TTemplateParser.ruleIncludeStmt: IStmt; var LSymbol: ITemplateSymbol; - LMatchSymbol: TTemplateSymbol; LIncludeExpr: IExpr; - LExtendsName: string; LScopeExpr: IExpr; LContainerTemplate: TTemplate; begin @@ -1061,7 +1063,7 @@ function TTemplateParser.ruleAssignStmt(ASymbol: IExpr): IStmt; function TTemplateParser.ruleBlockStmt: IStmt; var - LId: string; + LName: IExpr; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; @@ -1071,7 +1073,7 @@ function TTemplateParser.ruleBlockStmt: IStmt; LSymbol := FLookahead; match(vsBlock); - LId := matchValue(vsString); + LName := ruleExpression; match(vsEndScript); PushContainer; @@ -1083,7 +1085,7 @@ function TTemplateParser.ruleBlockStmt: IStmt; match(vsEndScript); PopContainer; - exit(TBlockStmt.Create(LSymbol.Position, LId, LContainer)); + exit(TBlockStmt.Create(LSymbol.Position, LName, LContainer)); end; @@ -1615,18 +1617,11 @@ function TTemplateParser.ruleExprStmt: IStmt; function TTemplateParser.ruleExtendsStmt: IStmt; var - LName: string; + LName: IExpr; LExpr: IExpr; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LBlockResolver: IBlockResolverVisitor; - LBlockReplacer: IBlockReplacerVisitor; - LBlockName: string; - LBlockNames: TArray; - LReplacementBlocks: TArray; - LTemplate: ITemplate; - LBlock: IBlockStmt; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); @@ -1634,10 +1629,7 @@ function TTemplateParser.ruleExtendsStmt: IStmt; match(vsExtends); match(vsOpenRoundBracket); - LName := matchValue(vsString); - - if not FContext.TryGetTemplate(LName, LTemplate) then - RaiseErrorRes(LSymbol.Position, @STemplateNotFound, [LName]); + LName := ruleExpression; if FLookahead.Token = vsComma then begin @@ -1659,21 +1651,7 @@ function TTemplateParser.ruleExtendsStmt: IStmt; PopContainer; - LBlockResolver := TBlockResolverVisitor.Create(); - AcceptVisitor(LContainer, LBlockResolver); - LBlockNames := LBlockResolver.GetBlockNames; - - LBlockReplacer := TBlockReplacerVisitor.Create(); - - for LBlockName in LBlockNames do - begin - LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); - for LBlock in LReplacementBlocks do - begin - LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); - end; - end; - exit(TExtendsStmt.Create(LSymbol.Position, LName, LExpr, LTemplate)); + exit(TExtendsStmt.Create(LSymbol.Position, LName, LExpr, LContainer)); end; function TTemplateParser.CurrentContainer: ITemplate; @@ -2622,7 +2600,7 @@ function TBlockStmt.Clone: IInterface; exit(TBlockStmt.Create(FPosition, FName, FContainer)); end; -constructor TBlockStmt.Create(const APosition: IPosition; const AName: string; const AContainer: ITemplate); +constructor TBlockStmt.Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); begin inherited Create(APosition); FName := AName; @@ -2634,11 +2612,16 @@ function TBlockStmt.GetContainer: ITemplate; exit(FContainer); end; -function TBlockStmt.GetName: string; +function TBlockStmt.GetName: IExpr; begin exit(FName); end; +function TBlockStmt.NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; +begin + exit(AEvalVisitor.EvalExprAsString(FName)); +end; + procedure TBlockStmt.SetContainer(const AContainer: ITemplate); begin FContainer := AContainer; @@ -2653,15 +2636,20 @@ procedure TExtendsStmt.Accept(const AVisitor: ITemplateVisitor); function TExtendsStmt.Clone: IInterface; begin - exit(TExtendsStmt.Create(FPosition, FName, FExpr, CloneTemplate(FContainer))); + exit(TExtendsStmt.Create(FPosition, FName, FExpr, FBlockContainer)); end; -constructor TExtendsStmt.Create(const APosition: IPosition; const AName: string; const AExpr: IExpr; const AContainer: ITemplate); +constructor TExtendsStmt.Create(const APosition: IPosition; const AName, AExpr: IExpr; const ABlockContainer: ITemplate); begin inherited Create(APosition); + FBlockContainer := ABlockContainer; FName := AName; FExpr := AExpr; - FContainer := CloneTemplate(AContainer); +end; + +function TExtendsStmt.GetBlockContainer: ITemplate; +begin + exit(FBlockContainer); end; function TExtendsStmt.GetContainer: ITemplate; @@ -2674,11 +2662,16 @@ function TExtendsStmt.GetExpr: IExpr; exit(FExpr); end; -function TExtendsStmt.GetName: string; +function TExtendsStmt.GetName: IExpr; begin exit(FName); end; +function TExtendsStmt.NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; +begin + exit(AEvalVisitor.EvalExprAsString(FName)); +end; + procedure TExtendsStmt.SetContainer(const AContainer: ITemplate); begin FContainer := AContainer; diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 10d41ea..3e5354e 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -558,7 +558,9 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDebugStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IBlockStmt); begin tab(); - write('<% block ''' + AStmt.Name + '''%>'); + write('<% block '''); + AcceptVisitor(AStmt.Name, self); + writeln('''%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); @@ -569,7 +571,9 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IBlockStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IExtendsStmt); begin tab(); - write('<% extends ''' + AStmt.Name + '''%>'); + write('<% extends '''); + AcceptVisitor(AStmt.Name, self); + writeln('''%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index d53e6bc..52ed77a 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -60,10 +60,10 @@ TTestTemplateInclude = class [Test] procedure TestSubTemplate; - [Test] + [Test, Ignore] procedure TestExtends; - [Test] + [Test, Ignore] procedure TestExtendsBlock; end; From 62ef1b9f49bd3ea55bb13b517045c96662046a23 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 13:01:46 +0100 Subject: [PATCH 006/138] update documentation --- README.md | 4 ++-- docs/commercial.license.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db8aaa9..22b0044 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,9 @@ Review [contibution terms and conditions](./docs/CONTRIBUTION.pdf) to contribute # License The Sempare Template Engine is dual-licensed. You may choose to use it under the restrictions of the [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) at -no cost to you, or you may purchase for user under the [Sempare Limited Commercial License](./docs/commercial.license.md) +no cost to you, or you may purchase for use under the [Sempare Limited Commercial License](./docs/commercial.license.md) -The dual-licensing scheme allows you to test the library with no restrictions, but subject to the terms of the GPL. A nominal fee is requested to support the maintenance of the library if the product is to be used in commercial products. This support fee binds you to the commercial license, removing any of the GPL restrictions, and allowing you to use the library in your products as you will. +The dual-licensing scheme allows you to test the library with no restrictions, but subject to the terms of the GPL. A nominal fee is requested to support the maintenance of the library if the product is to be used in commercial products. This support fee binds you to the commercial license, removing any of the GPL restrictions, and allowing you to use the library in your products as you will. The Sempare Template Engine may not be included/distributed as part of another commercial library without approval / commercial review. A commercial licence grants you the right to use Sempare Template Engine in your own applications, royalty free, and without any requirement to disclose your source code nor any modifications to Sempare Templte Engine to any other party. A commercial licence lasts into perpetuity, and entitles you to all future updates - free of charge. diff --git a/docs/commercial.license.md b/docs/commercial.license.md index ae76391..edf0dd4 100644 --- a/docs/commercial.license.md +++ b/docs/commercial.license.md @@ -8,6 +8,8 @@ The commercial license for Sempare Template Engine for Delphi gives you the righ - use the component and source code on all development systems used by the developer - sell any number of applications in any quantity without any additional run-time fees required +The Sempare Template Engine may not be included/distributed as part of another commercial library without approval / commercial review. + A commercial licence is sold per developer developing applications that use Sempare Template Engine for Delphi. The initial cost is $70 per developer and includes first year of support. For support thereafter, at your discretion, a support fee of $30 per developer per year would be appreciated (the cost of a few cups of coffee). Please contact us From 83bcb817908f7ca36adfb54ed50a11d823815297 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 14:01:00 +0100 Subject: [PATCH 007/138] Improve extends to be processed at evaluation time and to support the scoped expression Removed unnecessary casts of TValue Refactored expression evaluation with EvalExpr methods Ammended ignored tests to be evaluated --- src/Sempare.Template.AST.pas | 5 +- src/Sempare.Template.BlockReplacer.pas | 2 +- src/Sempare.Template.BlockResolver.pas | 5 +- src/Sempare.Template.Common.pas | 28 +++++ src/Sempare.Template.Evaluate.pas | 159 ++++++++++++------------- src/Sempare.Template.Parser.pas | 65 +++++----- tests/Sempare.Template.Test.pas | 9 +- tests/Sempare.Template.TestIf.pas | 2 +- tests/Sempare.Template.TestInclude.pas | 148 +++++++++++++++++------ 9 files changed, 260 insertions(+), 163 deletions(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index 3a03bb3..dda61cb 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -218,11 +218,12 @@ ETemplate = class(Exception); function GetBlockContainer: ITemplate; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); - function GetExpr: IExpr; + function GetBlockNames: TArray; + procedure SetBlockNames(const ANames: TArray); property Name: IExpr read GetName; - property Expr: IExpr read GetExpr; property Container: ITemplate read GetContainer write SetContainer; property BlockContainer: ITemplate read GetBlockContainer; + property BlockNames: TArray read GetBlockNames write SetBlockNames; end; IBlockStmt = interface(IStmt) diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index a0ba77b..b483af3 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -126,7 +126,7 @@ procedure TBlockReplacerVisitor.Visit(const AStmt: IBlockStmt); if not assigned(AStmt.Container) then exit; AcceptVisitor(AStmt.Container, self); - if AStmt.Name = FBlockName then + if FEvalVisitor.EvalExprAsString(AStmt.Name) = FBlockName then begin AStmt.Container := FReplacementBlock; end; diff --git a/src/Sempare.Template.BlockResolver.pas b/src/Sempare.Template.BlockResolver.pas index ac4e695..8781dad 100644 --- a/src/Sempare.Template.BlockResolver.pas +++ b/src/Sempare.Template.BlockResolver.pas @@ -53,7 +53,7 @@ TBlockResolverVisitor = class(TBaseTemplateVisitor, IBlockResolverVisitor) FBlocks: TDictionary>; FEvalVisitor: IEvaluationTemplateVisitor; public - constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); + constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor; const ATemplate: ITemplate); destructor Destroy; override; function GetBlockNames: TArray; @@ -142,10 +142,11 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); AcceptVisitor(AStmt.Container, self); end; -constructor TBlockResolverVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor); +constructor TBlockResolverVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor; const ATemplate: ITemplate); begin FEvalVisitor := AEvalVisitor; FBlocks := TDictionary < string, TArray < IBlockStmt >>.Create(); + AcceptVisitor(ATemplate, self); end; destructor TBlockResolverVisitor.Destroy; diff --git a/src/Sempare.Template.Common.pas b/src/Sempare.Template.Common.pas index 16f0bcc..4fa9679 100644 --- a/src/Sempare.Template.Common.pas +++ b/src/Sempare.Template.Common.pas @@ -113,8 +113,36 @@ procedure RaiseError(const APositional: IPosition; const AFormat: string); overl procedure RaiseErrorRes(const APositional: IPosition; const ResStringRec: PResStringRec; const AArgs: array of const); overload; procedure RaiseErrorRes(const APositional: IPosition; const ResStringRec: PResStringRec); overload; +function CloneTemplate(const ATemplate: ITemplate): ITemplate; +function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; +function CloneStmt(const AStmt: IStmt): IStmt; + implementation +function CloneTemplate(const ATemplate: ITemplate): ITemplate; +var + LIntf: IInterface; +begin + LIntf := ATemplate.Clone; + supports(LIntf, ITemplate, result); +end; + +function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; +var + LIntf: IInterface; +begin + LIntf := AVisitorHost.Clone; + supports(LIntf, ITemplateVisitorHost, result); +end; + +function CloneStmt(const AStmt: IStmt): IStmt; +var + LIntf: IInterface; +begin + LIntf := AStmt.Clone; + supports(LIntf, IStmt, result); +end; + function AsVisitorHost(const ATemplate: ITemplate): ITemplateVisitorHost; overload; begin ATemplate.QueryInterface(ITemplateVisitorHost, result); diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 9b2655b..0833166 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -194,8 +194,6 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IValueExpr); LValue: TValue; begin LValue := AExpr.Value; - if LValue.IsType then - LValue := LValue.AsType(); FEvalStack.push(LValue); end; @@ -225,16 +223,12 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IVariableDerefExpr); begin LAllowRootDeref := Preserve.Value(FAllowRootDeref, true); - AcceptVisitor(AExpr.variable, self); - LDerefObj := FEvalStack.pop; + LDerefObj := EvalExpr(AExpr.variable); LAllowRootDeref.SetValue(AExpr.DerefType = dtArray); - AcceptVisitor(AExpr.DerefExpr, self); - LDerefKey := FEvalStack.pop; - + LDerefKey := EvalExpr(AExpr.DerefExpr); LDerefedValue := Deref(Position(AExpr), LDerefObj, LDerefKey, eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext); - if LDerefedValue.IsType then - LDerefedValue := LDerefedValue.AsType(); + FEvalStack.push(LDerefedValue); end; @@ -244,10 +238,8 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IBinopExpr); LRight: TValue; LResult: TValue; begin - AcceptVisitor(AExpr.LeftExpr, self); - AcceptVisitor(AExpr.RightExpr, self); - LRight := FEvalStack.pop; - LLeft := FEvalStack.pop; + LLeft := EvalExpr(AExpr.LeftExpr); + LRight := EvalExpr(AExpr.RightExpr); case AExpr.BinOp of boIN: LResult := contains(Position(AExpr.RightExpr), LLeft, LRight, FContext); @@ -317,8 +309,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IUnaryExpr); var LValue: TValue; begin - AcceptVisitor(AExpr.Condition, self); - LValue := FEvalStack.pop; + LValue := EvalExpr(AExpr.Condition); case AExpr.UnaryOp of uoMinus: begin @@ -361,8 +352,6 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IVariableExpr); begin LValue := AExpr.variable; end; - if LValue.IsType then - LValue := LValue.AsType(); FEvalStack.push(LValue); end; @@ -386,8 +375,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IWhileStmt); end else begin - AcceptVisitor(AExpr, self); - exit(AsInt(FEvalStack.pop, FContext)); + exit(EvalExprAsInt(AExpr)); end; end; @@ -406,8 +394,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IWhileStmt); while ((LLimit = -1) or (LLoops < LLimit)) do begin - AcceptVisitor(AStmt.Condition, self); - if not AsBoolean(FEvalStack.pop) or (coBreak in FLoopOptions) then + if not EvalExprAsBoolean(AStmt.Condition) or (coBreak in FLoopOptions) then break; CheckRunTime(Position(AStmt)); if (LOffset = -1) or (i >= LOffset) then @@ -616,8 +603,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); end else begin - AcceptVisitor(AExpr, self); - exit(AsInt(FEvalStack.pop, FContext)); + exit(EvalExprAsInt(AExpr)); end; end; @@ -628,11 +614,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); FStackFrames.push(FStackFrames.peek.Clone); LVariableName := AStmt.variable; - AcceptVisitor(AStmt.Expr, self); - LLoopExpr := FEvalStack.pop; - if LLoopExpr.IsType then - LLoopExpr := LLoopExpr.AsType(); - + LLoopExpr := EvalExpr(AStmt.Expr); LOffset := GetValue(AStmt.OffsetExpr); LLimit := GetValue(AStmt.LimitExpr); @@ -749,7 +731,6 @@ function TEvaluationTemplateVisitor.ExprListArgs(const AExprList: IExprList): TA LCount: integer; begin AExprList.Accept(self); - LCount := AsInt(FEvalStack.pop(), FContext); if LCount <> AExprList.Count then // this should not happen RaiseError(nil, SNumberOfArgsMismatch); @@ -797,8 +778,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForRangeStmt); end else begin - AcceptVisitor(AExpr, self); - exit(AsInt(FEvalStack.pop, FContext)); + exit(EvalExprAsInt(AExpr)); end; end; @@ -807,10 +787,8 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForRangeStmt); exit; LVariable := AStmt.variable; LLoopOptions := Preserve.Value(FLoopOptions, []); - AcceptVisitor(AStmt.LowExpr, self); - LStartVal := AsInt(FEvalStack.pop, FContext); - AcceptVisitor(AStmt.HighExpr, self); - LEndVal := AsInt(FEvalStack.pop, FContext); + LStartVal := EvalExprAsInt(AStmt.LowExpr); + LEndVal := EvalExprAsInt(AStmt.HighExpr); LStep := GetValue(AStmt.StepExpr); LFirst := true; @@ -881,22 +859,20 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IAssignStmt); LValue: TValue; begin LVariable := AStmt.variable; - AcceptVisitor(AStmt.Expr, self); - LValue := FEvalStack.pop; - if LValue.IsType then - LValue := LValue.AsType; + LValue := EvalExpr(AStmt.Expr); FStackFrames.peek[LVariable] := LValue; end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIfStmt); var LExpr: TValue; + LIsObject: boolean; begin if HasBreakOrContinue then exit; - AcceptVisitor(AStmt.Condition, self); - LExpr := FEvalStack.pop; - if LExpr.IsObject and not IsEmptyObject(LExpr.AsObject) or not LExpr.IsObject and AsBoolean(LExpr) then + LExpr := EvalExpr(AStmt.Condition); + LIsObject := LExpr.IsObject; + if LIsObject and not IsEmptyObject(LExpr.AsObject) or not LIsObject and AsBoolean(LExpr) then AcceptVisitor(AStmt.TrueContainer, self) else if AStmt.FalseContainer <> nil then AcceptVisitor(AStmt.FalseContainer, self); @@ -914,9 +890,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIncludeStmt); begin if HasBreakOrContinue then exit; - AcceptVisitor(AStmt.Expr, self); - LTemplateName := FEvalStack.pop.AsString; - + LTemplateName := EvalExprAsString(AStmt.Expr); if FLocalTemplates.TryGetValue(LTemplateName, LTemplate) or FContext.TryGetTemplate(LTemplateName, LTemplate) then begin FStackFrames.push(FStackFrames.peek.Clone()); @@ -934,8 +908,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IPrintStmt); begin if HasBreakOrContinue then exit; - AcceptVisitor(AStmt.Expr, self); - FStreamWriter.Write(AsString(FEvalStack.pop, FContext)); + FStreamWriter.Write(EvalExprAsString(AStmt.Expr)); end; function CastArg(const AValue: TValue; const AType: TRttiType; const AContext: ITemplateContext): TValue; @@ -986,8 +959,6 @@ function GetArgs(const APosition: IPosition; const AMethod: TRttiMethod; const A begin LParameter := LParams[LParamIdx]; LParamValue := CastArg(AArgs[LParamIdx - LOffset], LParameter.ParamType, AContext); - if LParamValue.IsType then - LParamValue := LParamValue.AsType(); result[LParamIdx] := LParamValue; end; end; @@ -1082,8 +1053,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IMethodCallExpr); procedure TEvaluationTemplateVisitor.Visit(const AExpr: IEncodeExpr); begin - AcceptVisitor(AExpr.Expr, self); - FEvalStack.push(EncodeVariable(FEvalStack.pop)); + FEvalStack.push(EncodeVariable(EvalExpr(AExpr.Expr))); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); @@ -1109,26 +1079,16 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); -var - LTemplateName: TValue; begin - AcceptVisitor(AStmt.Name, self); - - LTemplateName := FEvalStack.pop; - AssertString(Position(AStmt), LTemplateName); - - FLocalTemplates.AddOrSetValue(AsString(LTemplateName, FContext), AStmt.Container); + FLocalTemplates.AddOrSetValue(EvalExprAsString(AStmt.Name), AStmt.Container); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IWithStmt); var LStackFrame: TStackFrame; - LExpr: TValue; begin - AcceptVisitor(AStmt.Expr, self); - LExpr := FEvalStack.pop; - LStackFrame := FStackFrames.peek.Clone(LExpr); + LStackFrame := FStackFrames.peek.Clone(EvalExpr(AStmt.Expr)); FStackFrames.push(LStackFrame); AcceptVisitor(AStmt.Container, self); @@ -1161,8 +1121,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IArrayExpr); procedure TEvaluationTemplateVisitor.Visit(const AExpr: ITernaryExpr); begin - AcceptVisitor(AExpr.Condition, self); - if AsBoolean(FEvalStack.pop) then + if EvalExprAsBoolean(AExpr.Condition) then AcceptVisitor(AExpr.TrueExpr, self) else AcceptVisitor(AExpr.FalseExpr, self); @@ -1172,7 +1131,6 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICycleStmt); var LStackFrame: TStackFrame; LValue: TValue; - LIdx: int64; begin try LStackFrame := FStackFrames.peek; @@ -1180,9 +1138,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICycleStmt); except RaiseErrorRes(Position(AStmt), @SCycleStatementMustBeInALoop); end; - LIdx := LValue.AsInt64; - AcceptVisitor(AStmt.List[LIdx mod AStmt.List.Count], self); - FStreamWriter.Write(AsString(FEvalStack.pop, FContext)); + FStreamWriter.Write(EvalExprAsString(AStmt.List[LValue.AsInt64 mod AStmt.List.Count])); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDebugStmt); @@ -1204,6 +1160,23 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IBlockStmt); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); + + function CommonNames(const ANames: TArray; const BNames: TArray): TArray; + var + a, b: string; + begin + result := nil; + // not mega optimal, but lists should be sort + for a in ANames do + begin + for b in BNames do + begin + if a = b then + insert(a, result, length(result)); + end; + end; + end; + var LName: string; LSymbol: ITemplateSymbol; @@ -1211,32 +1184,54 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); LBlockReplacer: IBlockReplacerVisitor; LBlockName: string; LBlockNames: TArray; + LTemplateNames: TArray; LReplacementBlocks: TArray; LTemplate: ITemplate; LBlock: IBlockStmt; + LResolveNames: boolean; begin - if assigned(AStmt.Container) then + LResolveNames := false; + if not assigned(AStmt.Container) then begin - if not FContext.TryGetTemplate(LName, LTemplate) then + LName := AStmt.NameAsString(self); + if not FLocalTemplates.TryGetValue(LName, LTemplate) and not FContext.TryGetTemplate(LName, LTemplate) then RaiseErrorRes(LSymbol.Position, @STemplateNotFound, [LName]); - AcceptVisitor(AStmt.Container, self); + AStmt.Container := LTemplate; + LResolveNames := true; + + end + else + begin + LTemplate := AStmt.Container; + end; + + // we need to clone the original as we do replacements + // not currently planning to support 'inherited', to allow to reference the original body in the original template from within the child body. + LTemplate := CloneTemplate(LTemplate); - LBlockResolver := TBlockResolverVisitor.Create(self); - AcceptVisitor(AStmt.BlockContainer, LBlockResolver); + if LResolveNames then + begin + LBlockResolver := TBlockResolverVisitor.Create(self, AStmt.Container); + LTemplateNames := LBlockResolver.GetBlockNames(); - LBlockNames := LBlockResolver.GetBlockNames; + LBlockResolver := TBlockResolverVisitor.Create(self, AStmt.BlockContainer); + LBlockNames := LBlockResolver.GetBlockNames(); + AStmt.BlockNames := CommonNames(LTemplateNames, LBlockNames); + end; - LBlockReplacer := TBlockReplacerVisitor.Create(self); - for LBlockName in LBlockNames do + // we resolve the blocks aimed for replacing defined withing the block container. + LBlockReplacer := TBlockReplacerVisitor.Create(self); + for LBlockName in AStmt.BlockNames do + begin + LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); + for LBlock in LReplacementBlocks do begin - LReplacementBlocks := LBlockResolver.GetBlocks(LBlockName); - for LBlock in LReplacementBlocks do - begin - LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); - end; + LBlockReplacer.Replace(LTemplate, LBlockName, LBlock.Container); end; end; + + AcceptVisitor(LTemplate, self); end; procedure TEvaluationTemplateVisitor.VisitContainer(const AContainer: ITemplate); diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 46edf66..40e382c 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -116,17 +116,18 @@ TEndStmt = class(TAbstractStmt, IEndStmt) TExtendsStmt = class(TAbstractStmt, IExtendsStmt) private FName: IExpr; - FExpr: IExpr; FBlockContainer: ITemplate; // NOTE: FContainer is something that is resolved FContainer: ITemplate; // NOTE: FContainer is something that is resolved + FBlockNames: TArray; // NOTE: names in the container function GetName: IExpr; - function GetExpr: IExpr; function GetBlockContainer: ITemplate; function GetContainer: ITemplate; procedure SetContainer(const AContainer: ITemplate); + function GetBlockNames: TArray; + procedure SetBlockNames(const ANames: TArray); function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; public - constructor Create(const APosition: IPosition; const AName, AExpr: IExpr; const ABlockContainer: ITemplate); + constructor Create(const APosition: IPosition; const AName: IExpr; const ABlockContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; function Clone: IInterface; override; end; @@ -538,30 +539,6 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Parse(const AStream: TStream; const AManagedStream: boolean): ITemplate; end; -function CloneTemplate(const ATemplate: ITemplate): ITemplate; -var - LIntf: IInterface; -begin - LIntf := ATemplate.Clone; - supports(LIntf, ITemplate, result); -end; - -function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; -var - LIntf: IInterface; -begin - LIntf := AVisitorHost.Clone; - supports(LIntf, ITemplateVisitorHost, result); -end; - -function CloneStmt(const AStmt: IStmt): IStmt; -var - LIntf: IInterface; -begin - LIntf := AStmt.Clone; - supports(LIntf, IStmt, result); -end; - function CreateTemplateParser(AContext: ITemplateContext): ITemplateParser; begin exit(TTemplateParser.Create(AContext)); @@ -1618,10 +1595,11 @@ function TTemplateParser.ruleExprStmt: IStmt; function TTemplateParser.ruleExtendsStmt: IStmt; var LName: IExpr; - LExpr: IExpr; + LScopeExpr: IExpr; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; + LContainerTemplate: TTemplate; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); @@ -1634,7 +1612,7 @@ function TTemplateParser.ruleExtendsStmt: IStmt; if FLookahead.Token = vsComma then begin match(vsComma); - LExpr := ruleExpression; + LScopeExpr := ruleExpression; end; match(vsCloseRoundBracket); @@ -1651,7 +1629,16 @@ function TTemplateParser.ruleExtendsStmt: IStmt; PopContainer; - exit(TExtendsStmt.Create(LSymbol.Position, LName, LExpr, LContainer)); + if LScopeExpr <> nil then + begin + LContainerTemplate := TTemplate.Create(); + LContainerTemplate.Add(TExtendsStmt.Create(LSymbol.Position, LName, LContainer)); + exit(TWithStmt.Create(LSymbol.Position, LScopeExpr, LContainerTemplate)); + end + else + begin + exit(TExtendsStmt.Create(LSymbol.Position, LName, LContainer)); + end; end; function TTemplateParser.CurrentContainer: ITemplate; @@ -2636,15 +2623,14 @@ procedure TExtendsStmt.Accept(const AVisitor: ITemplateVisitor); function TExtendsStmt.Clone: IInterface; begin - exit(TExtendsStmt.Create(FPosition, FName, FExpr, FBlockContainer)); + exit(TExtendsStmt.Create(FPosition, FName, FBlockContainer)); end; -constructor TExtendsStmt.Create(const APosition: IPosition; const AName, AExpr: IExpr; const ABlockContainer: ITemplate); +constructor TExtendsStmt.Create(const APosition: IPosition; const AName: IExpr; const ABlockContainer: ITemplate); begin inherited Create(APosition); FBlockContainer := ABlockContainer; FName := AName; - FExpr := AExpr; end; function TExtendsStmt.GetBlockContainer: ITemplate; @@ -2652,14 +2638,14 @@ function TExtendsStmt.GetBlockContainer: ITemplate; exit(FBlockContainer); end; -function TExtendsStmt.GetContainer: ITemplate; +function TExtendsStmt.GetBlockNames: TArray; begin - exit(FContainer); + exit(FBlockNames); end; -function TExtendsStmt.GetExpr: IExpr; +function TExtendsStmt.GetContainer: ITemplate; begin - exit(FExpr); + exit(FContainer); end; function TExtendsStmt.GetName: IExpr; @@ -2672,6 +2658,11 @@ function TExtendsStmt.NameAsString(const AEvalVisitor: IEvaluationTemplateVisito exit(AEvalVisitor.EvalExprAsString(FName)); end; +procedure TExtendsStmt.SetBlockNames(const ANames: TArray); +begin + FBlockNames := ANames; +end; + procedure TExtendsStmt.SetContainer(const AContainer: ITemplate); begin FContainer := AContainer; diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 1f3a963..d4e17a9 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -66,8 +66,7 @@ TTestTemplate = class procedure TestVariableNotFound; [Test] procedure TestArray; - [Test, Ignore] - // This is ignored because this is a potential future feature that is not currently supported. + [Test] procedure TestStmts; [Test] procedure TestRequire; @@ -352,7 +351,11 @@ procedure TTestTemplate.TestStartEndToken; procedure TTestTemplate.TestStmts; begin - Assert.AreEqual('1', Template.Eval('<% a := 1; print(a) %>')); + Assert.WillRaise( + procedure + begin // statement seperator is not supported. Need to review statment parsing to support this + Assert.AreEqual('1', Template.Eval('<% a := 1; print(a) %>')); + end); end; procedure TTestTemplate.TestStripWSScripts; diff --git a/tests/Sempare.Template.TestIf.pas b/tests/Sempare.Template.TestIf.pas index 868214b..d5d0165 100644 --- a/tests/Sempare.Template.TestIf.pas +++ b/tests/Sempare.Template.TestIf.pas @@ -69,7 +69,7 @@ TTestTemplateIf = class procedure TestIfList; [Test] procedure TestIfDict; - [Test{$IFDEF SEMPARE_TEMPLATE_FIREDAC}, Ignore {$ENDIF}] + [Test{$IFNDEF SEMPARE_TEMPLATE_FIREDAC}, Ignore {$ENDIF}] procedure TestIfDataSet; end; diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index 52ed77a..6ccb3e6 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -60,11 +60,24 @@ TTestTemplateInclude = class [Test] procedure TestSubTemplate; - [Test, Ignore] + [Test] procedure TestExtends; - [Test, Ignore] + [Test] procedure TestExtendsBlock; + + [Test] + procedure TestExtendsBlockWithDynamicNames; + + [Test] + procedure TestExtendsWebLike; + + [Test] + procedure TestExtendsNested; + + [Test] + procedure TestExtendsScopedExpr; + end; implementation @@ -273,54 +286,119 @@ TTemplate = record end; procedure TTestTemplateInclude.TestExtends; -var - LTpl: ITemplate; - LCtx: ITemplateContext; begin - LCtx := Template.Context(); - LCtx.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate - begin - if AName = 'showmember' then - begin - exit(Template.parse(AContext, '<% block ''content'' %>parent<% end %>')); - end - else - exit(nil); - end; - - LTpl := Template.parse(LCtx, // + Assert.AreEqual('parent parent', Template.Eval( // + '<% template ''showmember'' %>' + // + '<% block ''content'' %>parent<% end %>' + // + '<% end %>' + // '<% extends (''showmember'') %>' + // '<% end %> ' + // '<% extends (''showmember'') %>' + // '<% end %>' // - ); - Assert.AreEqual('parent parent', Template.Eval(LTpl)); + )); end; procedure TTestTemplateInclude.TestExtendsBlock; -var - LTpl: ITemplate; - LCtx: ITemplateContext; begin - LCtx := Template.Context(); - LCtx.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate - begin - if AName = 'showmember' then - begin - exit(Template.parse(AContext, '<% block ''content'' %>parent<% end %>')); - end - else - exit(nil); - end; - LTpl := Template.parse(LCtx, // + Assert.AreEqual('child child2', Template.Eval( // + '<% template ''showmember'' %>' + // + '<% block ''content'' %>parent<% end %>' + // + '<% end %>' + // '<% extends (''showmember'') %>' + // '<% block ''content'' %>child<% end %>' + // '<% end %> ' + // '<% extends (''showmember'') %>' + // '<% block ''content'' %>child2<% end %>' + // '<% end %>' // - ); - Assert.AreEqual('child child2', Template.Eval(LTpl)); + )); +end; + +procedure TTestTemplateInclude.TestExtendsBlockWithDynamicNames; +begin + Assert.AreEqual('child child2', Template.Eval( // + '<% for i := 1 to 2 %>' + // + '<% template ''showmember'' + i %>' + // + '<% block ''content'' %>parent<% end %>' + // + '<% end %>' + // + '<% end %>' + // + '<% extends (''showmember'' + 1) %>' + // + '<% block ''content'' %>child<% end %>' + // + '<% end %> ' + // + '<% extends (''showmember'' + 2) %>' + // + '<% block ''content'' %>child2<% end %>' + // + '<% end %>' // + )); +end; + +procedure TTestTemplateInclude.TestExtendsWebLike; +begin + Assert.AreEqual('header content footer', Template.Eval( // + '<% template ''header'' %>' + // + 'header' + // + '<% end %>' + // + '<% template ''footer'' %>' + // + 'footer' + // + '<% end %>' + // + '<% template ''template'' %>' + // + '<% include(''header'') %> ' + // + '<% block ''body'' %>body<% end %> ' + // + '<% include(''footer'') %>' + // + '<% end %>' + // + '<% extends (''template'') %>' + // + '<% block ''body'' %>content<% end %>' + // + '<% end %>' // + )); +end; + +procedure TTestTemplateInclude.TestExtendsNested; +begin + Assert.AreEqual('header default header default body footer default footer', Template.Eval( // + '<% template ''header'' %>' + // + 'header <% block ''general'' %>default header<% end %>' + // + '<% end %>' + // + '<% template ''footer'' %>' + // + 'footer <% block ''general'' %>default footer<% end %>' + // + '<% end %>' + // + '<% template ''template'' %>' + // + '<% include(''header'') %> ' + // + '<% block ''general'' %>default body<% end %> ' + // + '<% include(''footer'') %>' + // + '<% end %>' + // + '<% extends (''template'') %>' + // + '<% end %>' // + )); + + Assert.AreEqual('header default header general footer default footer', Template.Eval( // + '<% template ''header'' %>' + // + 'header <% block ''general'' %>default header<% end %>' + // + '<% end %>' + // + '<% template ''footer'' %>' + // + 'footer <% block ''general'' %>default footer<% end %>' + // + '<% end %>' + // + '<% template ''template'' %>' + // + '<% include(''header'') %> ' + // + '<% block ''general'' %>default body<% end %> ' + // + '<% include(''footer'') %>' + // + '<% end %>' + // + '<% extends (''template'') %>' + // + '<% block ''general'' %>general<% end %>' + // + '<% end %>' // + )); +end; + +procedure TTestTemplateInclude.TestExtendsScopedExpr; +begin + Assert.AreEqual('first 1 second 2 ', Template.Eval( // + '<% template ''template'' %>' + // + '<% block ''general'' %>body<% end %> <% _ %> ' + // + '<% end %>' + // + '<% extends (''template'', 1) %>' + // + '<% block ''general'' %>first<% end %> ' + // + '<% end %>' + // + '<% extends (''template'', 2) %>' + // + '<% block ''general'' %>second<% end %> ' + // + '<% end %>' // + )); end; initialization From b2815aee2118a105fe2ac0e6de5c5fdfe440ecc4 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 14:06:36 +0100 Subject: [PATCH 008/138] Sempare.Template.Pkg update to include all sources --- Sempare.Template.Pkg.dpk | 28 +++++++++++++++------------- Sempare.Template.Pkg.dproj | 28 +++++++++++++++------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index 17d3e71..450ef8a 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -64,21 +64,23 @@ requires dbrtl; contains - Sempare.Template.Visitor in 'src\Sempare.Template.Visitor.pas', - Sempare.Template.Util in 'src\Sempare.Template.Util.pas', - Sempare.Template.StackFrame in 'src\Sempare.Template.StackFrame.pas', - Sempare.Template.Rtti in 'src\Sempare.Template.Rtti.pas', - Sempare.Template.PrettyPrint in 'src\Sempare.Template.PrettyPrint.pas', - Sempare.Template.Parser in 'src\Sempare.Template.Parser.pas', - Sempare.Template.Lexer in 'src\Sempare.Template.Lexer.pas', - Sempare.Template.Functions in 'src\Sempare.Template.Functions.pas', - Sempare.Template.Evaluate in 'src\Sempare.Template.Evaluate.pas', - Sempare.Template.Context in 'src\Sempare.Template.Context.pas', - Sempare.Template.Common in 'src\Sempare.Template.Common.pas', Sempare.Template.AST in 'src\Sempare.Template.AST.pas', - Sempare.Template in 'src\Sempare.Template.pas', + Sempare.Template.BlockReplacer in 'src\Sempare.Template.BlockReplacer.pas', + Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', + Sempare.Template.Common in 'src\Sempare.Template.Common.pas', + Sempare.Template.Context in 'src\Sempare.Template.Context.pas', + Sempare.Template.Evaluate in 'src\Sempare.Template.Evaluate.pas', + Sempare.Template.Functions in 'src\Sempare.Template.Functions.pas', Sempare.Template.JSON in 'src\Sempare.Template.JSON.pas', + Sempare.Template.Lexer in 'src\Sempare.Template.Lexer.pas', + Sempare.Template.Parser in 'src\Sempare.Template.Parser.pas', + Sempare.Template in 'src\Sempare.Template.pas', + Sempare.Template.PrettyPrint in 'src\Sempare.Template.PrettyPrint.pas', Sempare.Template.ResourceStrings in 'src\Sempare.Template.ResourceStrings.pas', - Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas'; + Sempare.Template.Rtti in 'src\Sempare.Template.Rtti.pas', + Sempare.Template.StackFrame in 'src\Sempare.Template.StackFrame.pas', + Sempare.Template.Util in 'src\Sempare.Template.Util.pas', + Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas', + Sempare.Template.Visitor in 'src\Sempare.Template.Visitor.pas'; end. diff --git a/Sempare.Template.Pkg.dproj b/Sempare.Template.Pkg.dproj index e530430..844a3a6 100644 --- a/Sempare.Template.Pkg.dproj +++ b/Sempare.Template.Pkg.dproj @@ -90,23 +90,25 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + From 838f9eba13af33947ab64dc34bbb89cfc1c869e4 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 14:30:20 +0100 Subject: [PATCH 009/138] Minimise cloning required by extends --- src/Sempare.Template.Parser.pas | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 40e382c..0018280 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -1934,7 +1934,7 @@ procedure TForInStmt.Accept(const AVisitor: ITemplateVisitor); function TForInStmt.Clone: IInterface; begin - exit(TForInStmt.Create(FPosition, FVariable, FForOp, FExpr, FOffsetExpr, FLimitExpr, FContainer, FOnFirst, FOnLast, FOnEmpty, FBetweenItem)); + exit(TForInStmt.Create(FPosition, FVariable, FForOp, FExpr, FOffsetExpr, FLimitExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); end; constructor TForInStmt.Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const AExpr: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); @@ -2028,7 +2028,7 @@ procedure TAssignStmt.Accept(const AVisitor: ITemplateVisitor); function TAssignStmt.Clone: IInterface; begin - exit(TAssignStmt.Create(FPosition, FVariable, FExpr)); + exit(self); end; constructor TAssignStmt.Create(const APosition: IPosition; const AVariable: string; const AExpr: IExpr); @@ -2138,7 +2138,7 @@ procedure TContinueStmt.Accept(const AVisitor: ITemplateVisitor); function TContinueStmt.Clone: IInterface; begin - exit(TContinueStmt.Create(FPosition)); + exit(self); end; { TBreakStmt } @@ -2150,7 +2150,7 @@ procedure TBreakStmt.Accept(const AVisitor: ITemplateVisitor); function TBreakStmt.Clone: IInterface; begin - exit(TBreakStmt.Create(FPosition)); + exit(self); end; { TEndStmt } @@ -2162,7 +2162,7 @@ procedure TEndStmt.Accept(const AVisitor: ITemplateVisitor); function TEndStmt.Clone: IInterface; begin - exit(TEndStmt.Create(FPosition)); + exit(self); end; { TVariableDerefExpr } @@ -2198,7 +2198,7 @@ procedure TIncludeStmt.Accept(const AVisitor: ITemplateVisitor); function TIncludeStmt.Clone: IInterface; begin - exit(TIncludeStmt.Create(FPosition, FExpr)); + exit(self); end; { TElseStmt } @@ -2210,7 +2210,7 @@ procedure TElseStmt.Accept(const AVisitor: ITemplateVisitor); function TElseStmt.Clone: IInterface; begin - exit(TElseStmt.Create(FPosition)); + exit(self); end; { TElIfStmt } @@ -2222,7 +2222,7 @@ procedure TElIfStmt.Accept(const AVisitor: ITemplateVisitor); function TElIfStmt.Clone: IInterface; begin - exit(TElIfStmt.Create(FPosition)); + exit(self); end; { TCommentStmt } @@ -2234,7 +2234,7 @@ procedure TCommentStmt.Accept(const AVisitor: ITemplateVisitor); function TCommentStmt.Clone: IInterface; begin - exit(TCommentStmt.Create(FPosition)); + exit(self); end; { TAbstractBase } @@ -2433,7 +2433,7 @@ procedure TRequireStmt.Accept(const AVisitor: ITemplateVisitor); function TRequireStmt.Clone: IInterface; begin - exit(TRequireStmt.Create(FPosition, FExprList)); + exit(self); end; constructor TRequireStmt.Create(const APosition: IPosition; const AExprList: IExprList); @@ -2508,7 +2508,7 @@ procedure TCycleStmt.Accept(const AVisitor: ITemplateVisitor); function TCycleStmt.Clone: IInterface; begin - exit(TCycleStmt.Create(FPosition, FExprList)); + exit(self); end; constructor TCycleStmt.Create(const APosition: IPosition; const AList: IExprList); @@ -2584,7 +2584,7 @@ procedure TBlockStmt.Accept(const AVisitor: ITemplateVisitor); function TBlockStmt.Clone: IInterface; begin - exit(TBlockStmt.Create(FPosition, FName, FContainer)); + exit(TBlockStmt.Create(FPosition, FName, CloneTemplate(FContainer))); end; constructor TBlockStmt.Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); @@ -2623,7 +2623,7 @@ procedure TExtendsStmt.Accept(const AVisitor: ITemplateVisitor); function TExtendsStmt.Clone: IInterface; begin - exit(TExtendsStmt.Create(FPosition, FName, FBlockContainer)); + exit(TExtendsStmt.Create(FPosition, FName, CloneTemplate(FBlockContainer))); end; constructor TExtendsStmt.Create(const APosition: IPosition; const AName: IExpr; const ABlockContainer: ITemplate); From bef431b5c597325823d246861d77d5467f9e99d5 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 14:32:03 +0100 Subject: [PATCH 010/138] Add short circuit functionality in BlockReplacer to terminate as soon as replacement is done --- src/Sempare.Template.BlockReplacer.pas | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index b483af3..ec4b7bb 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -50,6 +50,7 @@ TBlockReplacerVisitor = class(TBaseTemplateVisitor, IBlockReplacerVisitor) FBlockName: string; FReplacementBlock: ITemplate; FEvalVisitor: IEvaluationTemplateVisitor; + FFound: boolean; public constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); procedure Visit(const AStmt: IIfStmt); overload; override; @@ -79,31 +80,43 @@ implementation procedure TBlockReplacerVisitor.Visit(const AStmt: IForRangeStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IProcessTemplateStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IDefineTemplateStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IWithStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IForInStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IIfStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.TrueContainer, self); if AStmt.FalseContainer <> nil then begin @@ -113,27 +126,36 @@ procedure TBlockReplacerVisitor.Visit(const AStmt: IIfStmt); procedure TBlockReplacerVisitor.Visit(const AStmt: IWhileStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IDebugStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Stmt, self); end; procedure TBlockReplacerVisitor.Visit(const AStmt: IBlockStmt); begin + if FFound then + exit; if not assigned(AStmt.Container) then exit; AcceptVisitor(AStmt.Container, self); if FEvalVisitor.EvalExprAsString(AStmt.Name) = FBlockName then begin + FFound := true; AStmt.Container := FReplacementBlock; end; end; procedure TBlockReplacerVisitor.Visit(const AStmt: IExtendsStmt); begin + if FFound then + exit; AcceptVisitor(AStmt.Container, self); end; @@ -146,6 +168,7 @@ procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlock var LVisitor: ITemplateVisitor; begin + FFound := false; if not supports(self, ITemplateVisitor, LVisitor) then exit; if not assigned(ABlock) then From 91ce3743a46d5074db9d766011d13235e6534025 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 14:59:22 +0100 Subject: [PATCH 011/138] Update documentation for extends/block support --- README.md | 1 + docs/configuration.md | 4 +- docs/images/stmt_block.svg | 149 ++++++++++++++++++++++++++++++++++++ docs/rail-road-diagrams.txt | 23 ++++++ docs/statements.md | 81 ++++++++++++++++++-- 5 files changed, 252 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 22b0044..04663f2 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ The playlist has a few videos that are very short (most less than a minute - bli - if, elif, else statements - for and while statements - include statement + - extends / block statements - with statement - function/method calls - expressions diff --git a/docs/configuration.md b/docs/configuration.md index 5aee343..3c1d0a2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -90,7 +90,9 @@ ctx.RegisterTemplate('footer', Template.Parse('Copyright (c) <% year %> <% compa ``` ### Dynamic Template Resolution -Templates don't need to be precompiled. They can also be located when being parsed by setting the template resolver property on the context: +Templates don't need to be located in a single template. They can also be resolved dynamically using the TemplateResolver method on the context. +Templates could be loaded from file, resources or urls are per your requirements. + ``` ctx.TemplateResolver = function(const AContext : ITemplate; const AName : string) : ITemplate begin diff --git a/docs/images/stmt_block.svg b/docs/images/stmt_block.svg index e69de29..7603ede 100644 --- a/docs/images/stmt_block.svg +++ b/docs/images/stmt_block.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + +<% + + + + + + + +block + + + + + + + +( + + + + + + + +expr + + + + + + + +) + + + + + + + +%> + + + + + + + + + + +block + + + + + + + +<% + + + + + + + +end + + + + + + + +%> + + + + + + + + + + diff --git a/docs/rail-road-diagrams.txt b/docs/rail-road-diagrams.txt index e96a5a0..f888b4f 100644 --- a/docs/rail-road-diagrams.txt +++ b/docs/rail-road-diagrams.txt @@ -538,4 +538,27 @@ Sequence( Terminal('%>'), ) +) + + + +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0AStack(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('(')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%20%20%0A%20%20%20%20%20%20%20Terminal(')')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A)%2C%0ASequence(%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%20Terminal('end')%2C%20Terminal('%25%3E')%2C%20%0A%20%20%20)%0A)%0A) + +Diagram( +Stack( +Sequence( + Terminal('<%'), + Terminal('block'), + Terminal('('), + NonTerminal('expr'), + Terminal(')'), + Terminal('%>'), +), +Sequence( + NonTerminal('block'), + Terminal('<%'), Terminal('end'), Terminal('%>'), + ) +) ) \ No newline at end of file diff --git a/docs/statements.md b/docs/statements.md index 799d061..24a3e45 100644 --- a/docs/statements.md +++ b/docs/statements.md @@ -10,11 +10,13 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) 4. [for](#for) 5. [while](#while) 6. [include](#include) -7. [with](#with) -8. [template](#template) -9. [require](#require) -10. [ignorenl](#ignorenl) -11. [cycle](#cycle) +7. [block](#block) +8. [extends](#extends) +9. [with](#with) +10. [template](#template) +11. [require](#require) +12. [ignorenl](#ignorenl) +13. [cycle](#cycle) ### print @@ -313,6 +315,75 @@ begin include() can also take a second parameter, allowing for improved scoping of variables, similar to the _with_ statement. +### block + +![block](./images/stmt_block.svg) + +You may want to decompose templates into reusable parts. You register templates on a Template context. + +``` +<% block 'content' %> +Some content +<% end %> +``` + +The block statment defines a placeholder in a template that can be replaced. When a block is contained within an extends block, it will be a replacement in the referenced template. + +### extends + +![extends](./images/stmt_extends.svg) + +extends() allows you to replace blocks within another template. + +``` +<% template 'header_footer' %> + + + My Page + + + <% block 'content'%>the content<% end %> + + +<% end %> +``` + +Without the extends statement, include('header_footer') would resolve to: +``` + + + My Page + + + the content + + +``` + +Using the extends statement... +``` +<% extends ('header_footer') %> + // content here will be ignored + <% block 'content' %>

Welcome to my page

<% end %> +<% end %> +``` +would resolve to: +``` + + + My Page + + + Welcome to my page + + +``` + + + + +extends() can also take a second parameter, allowing for improved scoping of variables, similar to the _with_ statement. + ### with ![with](./images/stmt_with.svg) From 840638dbaf3da803fb3470c054ed642a66717468 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 28 Mar 2023 15:01:30 +0100 Subject: [PATCH 012/138] Fix example in docs --- docs/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/statements.md b/docs/statements.md index 24a3e45..445e266 100644 --- a/docs/statements.md +++ b/docs/statements.md @@ -374,7 +374,7 @@ would resolve to: My Page - Welcome to my page +

Welcome to my page

``` From a91f43384909c231e4775640e917b55b32246a6f Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:37:11 +0100 Subject: [PATCH 013/138] extend AcceptVisitor overloads --- src/Sempare.Template.Common.pas | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Sempare.Template.Common.pas b/src/Sempare.Template.Common.pas index 4fa9679..4513305 100644 --- a/src/Sempare.Template.Common.pas +++ b/src/Sempare.Template.Common.pas @@ -103,6 +103,8 @@ function AsVisitorHost(const AStmt: IStmt): ITemplateVisitorHost; inline; overlo procedure AcceptVisitor(const ATemplate: ITemplate; const AVisitor: ITemplateVisitor); inline; overload; procedure AcceptVisitor(const AExpr: IExpr; const AVisitor: ITemplateVisitor); inline; overload; procedure AcceptVisitor(const AStmt: IStmt; const AVisitor: ITemplateVisitor); inline; overload; +procedure AcceptVisitor(const AExpr: IExprList; const AVisitor: ITemplateVisitor); overload; +procedure AcceptVisitor(const AHost: ITemplateVisitorHost; const AVisitor: ITemplateVisitor); overload; function Position(const AStmt: IStmt): IPosition; inline; overload; function Position(const AExpr: IExpr): IPosition; inline; overload; @@ -162,6 +164,8 @@ procedure AcceptVisitor(const ATemplate: ITemplate; const AVisitor: ITemplateVis var LHost: ITemplateVisitorHost; begin + if not assigned(ATemplate) then + exit; LHost := AsVisitorHost(ATemplate); LHost.Accept(AVisitor); end; @@ -170,6 +174,8 @@ procedure AcceptVisitor(const AExpr: IExpr; const AVisitor: ITemplateVisitor); var LHost: ITemplateVisitorHost; begin + if not assigned(AExpr) then + exit; LHost := AsVisitorHost(AExpr); LHost.Accept(AVisitor); end; @@ -178,10 +184,33 @@ procedure AcceptVisitor(const AStmt: IStmt; const AVisitor: ITemplateVisitor); var LHost: ITemplateVisitorHost; begin + if not assigned(AStmt) then + exit; LHost := AsVisitorHost(AStmt); LHost.Accept(AVisitor); end; +procedure AcceptVisitor(const AHost: ITemplateVisitorHost; const AVisitor: ITemplateVisitor); +begin + if not assigned(AHost) then + exit; + AHost.Accept(AVisitor); +end; + +procedure AcceptVisitor(const AExpr: IExprList; const AVisitor: ITemplateVisitor); +var + LHost: ITemplateVisitorHost; + i: integer; +begin + if not assigned(AExpr) then + exit; + for i := 0 to AExpr.Count - 1 do + begin + LHost := AsVisitorHost(AExpr[i]); + LHost.Accept(AVisitor); + end; +end; + function Position(const AStmt: IStmt): IPosition; overload; var LSymbol: IPositional; From bb08102598b7cab4713f3db95d07d00b74eac21c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:38:43 +0100 Subject: [PATCH 014/138] Regroup exprs in TBaseTemplateVisitor Add default visitor behavour to TBaseTemplateVisitor Add TNoExprTemplateVisitor where exprs are not visited --- src/Sempare.Template.Visitor.pas | 159 ++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 35 deletions(-) diff --git a/src/Sempare.Template.Visitor.pas b/src/Sempare.Template.Visitor.pas index b12d745..ec15665 100644 --- a/src/Sempare.Template.Visitor.pas +++ b/src/Sempare.Template.Visitor.pas @@ -55,6 +55,9 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AExprList: IExprList); overload; virtual; procedure Visit(const AExpr: ITernaryExpr); overload; virtual; procedure Visit(const AExpr: IArrayExpr); overload; virtual; + procedure Visit(const AStmt: IFunctionCallExpr); overload; virtual; + procedure Visit(const AStmt: IMethodCallExpr); overload; virtual; + procedure Visit(const AStmt: IEncodeExpr); overload; virtual; procedure Visit(const AStmt: IStmt); overload; virtual; procedure Visit(const AStmt: IAssignStmt); overload; virtual; @@ -64,14 +67,11 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AStmt: IEndStmt); overload; virtual; procedure Visit(const AStmt: IIncludeStmt); overload; virtual; procedure Visit(const AStmt: IRequireStmt); overload; virtual; - procedure Visit(const AStmt: IEncodeExpr); overload; virtual; procedure Visit(const AStmt: IPrintStmt); overload; virtual; procedure Visit(const AStmt: IIfStmt); overload; virtual; procedure Visit(const AStmt: IWhileStmt); overload; virtual; procedure Visit(const AStmt: IForInStmt); overload; virtual; procedure Visit(const AStmt: IForRangeStmt); overload; virtual; - procedure Visit(const AStmt: IFunctionCallExpr); overload; virtual; - procedure Visit(const AStmt: IMethodCallExpr); overload; virtual; procedure Visit(const AStmt: IProcessTemplateStmt); overload; virtual; procedure Visit(const AStmt: IDefineTemplateStmt); overload; virtual; procedure Visit(const AStmt: IWithStmt); overload; virtual; @@ -81,6 +81,21 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AStmt: IExtendsStmt); overload; virtual; end; + TNoExprTemplateVisitor = class(TBaseTemplateVisitor, ITemplateVisitor) + public + + procedure Visit(const AExpr: IBinopExpr); overload; override; + procedure Visit(const AExpr: IUnaryExpr); overload; override; + procedure Visit(const AExpr: IVariableDerefExpr); overload; override; + procedure Visit(const AExprList: IExprList); overload; override; + procedure Visit(const AExpr: ITernaryExpr); overload; override; + procedure Visit(const AExpr: IArrayExpr); overload; override; + procedure Visit(const AStmt: IFunctionCallExpr); overload; override; + procedure Visit(const AStmt: IMethodCallExpr); overload; override; + procedure Visit(const AStmt: IEncodeExpr); overload; override; + + end; + implementation uses @@ -91,20 +106,17 @@ implementation procedure TBaseTemplateVisitor.Visit(const AExpr: IVariableExpr); begin - + // don't do anything end; procedure TBaseTemplateVisitor.Visit(const AExpr: IValueExpr); begin - + // don't do anything end; procedure TBaseTemplateVisitor.Visit(const AExprList: IExprList); -var - LIndex: integer; begin - for LIndex := 0 to AExprList.Count - 1 do - AcceptVisitor(AExprList[LIndex], self); + AcceptVisitor(AExprList, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IStmt); @@ -114,7 +126,7 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IStmt); procedure TBaseTemplateVisitor.Visit(const AContainer: ITemplateVisitorHost); begin - + AcceptVisitor(AContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AExpr: IExpr); @@ -124,55 +136,78 @@ procedure TBaseTemplateVisitor.Visit(const AExpr: IExpr); procedure TBaseTemplateVisitor.Visit(const AExpr: IBinopExpr); begin - + AcceptVisitor(AExpr.LeftExpr, self); + AcceptVisitor(AExpr.RightExpr, self); end; procedure TBaseTemplateVisitor.Visit(const AExpr: IUnaryExpr); begin - + AcceptVisitor(AExpr.Expr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IIfStmt); begin - + AcceptVisitor(AStmt.Condition, self); + AcceptVisitor(AStmt.TrueContainer, self); + AcceptVisitor(AStmt.FalseContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IWhileStmt); begin + AcceptVisitor(AStmt.Condition, self); + AcceptVisitor(AStmt.Container, self); + AcceptVisitor(AStmt.OffsetExpr, self); + AcceptVisitor(AStmt.LimitExpr, self); + AcceptVisitor(AStmt.OnFirstContainer, self); + AcceptVisitor(AStmt.OnEndContainer, self); + AcceptVisitor(AStmt.OnEmptyContainer, self); + AcceptVisitor(AStmt.BetweenItemsContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IForInStmt); begin - + AcceptVisitor(AStmt.Expr, self); + AcceptVisitor(AStmt.Container, self); + AcceptVisitor(AStmt.OffsetExpr, self); + AcceptVisitor(AStmt.LimitExpr, self); + AcceptVisitor(AStmt.OnFirstContainer, self); + AcceptVisitor(AStmt.OnEndContainer, self); + AcceptVisitor(AStmt.OnEmptyContainer, self); + AcceptVisitor(AStmt.BetweenItemsContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IForRangeStmt); begin - + AcceptVisitor(AStmt.LowExpr, self); + AcceptVisitor(AStmt.HighExpr, self); + AcceptVisitor(AStmt.StepExpr, self); + AcceptVisitor(AStmt.Container, self); + AcceptVisitor(AStmt.OnFirstContainer, self); + AcceptVisitor(AStmt.OnEndContainer, self); + AcceptVisitor(AStmt.OnEmptyContainer, self); + AcceptVisitor(AStmt.BetweenItemsContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AContainer: ITemplate); -var - LIdx: integer; begin - for LIdx := 0 to AContainer.Count - 1 do - AContainer.Items[LIdx].Accept(self); + AcceptVisitor(AContainer, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IAssignStmt); begin - + AcceptVisitor(AStmt.Expr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IIncludeStmt); begin - + AcceptVisitor(AStmt.Expr, self); end; procedure TBaseTemplateVisitor.Visit(const AExpr: IVariableDerefExpr); begin - + AcceptVisitor(AExpr.Variable, self); + AcceptVisitor(AExpr.DerefExpr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IContinueStmt); @@ -192,12 +227,12 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IEndStmt); procedure TBaseTemplateVisitor.Visit(const AStmt: IPrintStmt); begin - + AcceptVisitor(AStmt.Expr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IFunctionCallExpr); begin - + AcceptVisitor(AStmt.ExprList, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IElseStmt); @@ -207,62 +242,116 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IElseStmt); procedure TBaseTemplateVisitor.Visit(const AStmt: IMethodCallExpr); begin - + AcceptVisitor(AStmt.ObjectExpr, self); + AcceptVisitor(AStmt.ExprList, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IEncodeExpr); begin - + AcceptVisitor(AStmt.Expr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); begin - + AcceptVisitor(AStmt.Container, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); begin - + AcceptVisitor(AStmt.Name, self); + AcceptVisitor(AStmt.Container, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IWithStmt); begin - + AcceptVisitor(AStmt.Expr, self); + AcceptVisitor(AStmt.Container, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IRequireStmt); begin - + AcceptVisitor(AStmt.ExprList, self); end; procedure TBaseTemplateVisitor.Visit(const AExpr: IArrayExpr); begin - + AcceptVisitor(AExpr.ExprList, self); end; procedure TBaseTemplateVisitor.Visit(const AExpr: ITernaryExpr); begin - + AcceptVisitor(AExpr.Condition, self); + AcceptVisitor(AExpr.TrueExpr, self); + AcceptVisitor(AExpr.FalseExpr, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: ICycleStmt); begin - + AcceptVisitor(AStmt.List, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IDebugStmt); begin - + AcceptVisitor(AStmt.Stmt, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IBlockStmt); begin - + AcceptVisitor(AStmt.Name, self); + AcceptVisitor(AStmt.Container, self); end; procedure TBaseTemplateVisitor.Visit(const AStmt: IExtendsStmt); begin + AcceptVisitor(AStmt.Name, self); + AcceptVisitor(AStmt.BlockContainer, self); +end; + +{ TNoExprTemplateVisitor } + +procedure TNoExprTemplateVisitor.Visit(const AExprList: IExprList); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AExpr: IVariableDerefExpr); +begin + +end; + +procedure TNoExprTemplateVisitor.Visit(const AExpr: IUnaryExpr); +begin + // do nothing +end; +procedure TNoExprTemplateVisitor.Visit(const AExpr: IBinopExpr); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AExpr: ITernaryExpr); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AStmt: IEncodeExpr); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AStmt: IMethodCallExpr); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AStmt: IFunctionCallExpr); +begin + // do nothing +end; + +procedure TNoExprTemplateVisitor.Visit(const AExpr: IArrayExpr); +begin + // do nothing end; end. From 1d93236067ef4f40a62814eacf90850d1491bb30 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:41:09 +0100 Subject: [PATCH 015/138] remove methods from visitor implementations where base class provides the basic traversal logic --- src/Sempare.Template.BlockReplacer.pas | 97 ++---------- src/Sempare.Template.BlockResolver.pas | 72 ++------- src/Sempare.Template.VariableExtraction.pas | 164 -------------------- 3 files changed, 30 insertions(+), 303 deletions(-) diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index ec4b7bb..c718255 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -45,24 +45,16 @@ interface procedure Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); end; - TBlockReplacerVisitor = class(TBaseTemplateVisitor, IBlockReplacerVisitor) + TBlockReplacerVisitor = class(TNoExprTemplateVisitor, IBlockReplacerVisitor) private FBlockName: string; FReplacementBlock: ITemplate; FEvalVisitor: IEvaluationTemplateVisitor; - FFound: boolean; + public constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); - procedure Visit(const AStmt: IIfStmt); overload; override; - procedure Visit(const AStmt: IWhileStmt); overload; override; - procedure Visit(const AStmt: IForInStmt); overload; override; - procedure Visit(const AStmt: IForRangeStmt); overload; override; - - procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; - procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; - procedure Visit(const AStmt: IWithStmt); overload; override; - procedure Visit(const AStmt: IDebugStmt); overload; override; + procedure Visit(const AStmt: IIncludeStmt); overload; override; procedure Visit(const AStmt: IBlockStmt); overload; override; procedure Visit(const AStmt: IExtendsStmt); overload; override; @@ -78,85 +70,21 @@ implementation { TBlockReplacerVisitor } -procedure TBlockReplacerVisitor.Visit(const AStmt: IForRangeStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IProcessTemplateStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IDefineTemplateStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IWithStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IForInStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IIfStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.TrueContainer, self); - if AStmt.FalseContainer <> nil then - begin - AcceptVisitor(AStmt.FalseContainer, self); - end; -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IWhileStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockReplacerVisitor.Visit(const AStmt: IDebugStmt); -begin - if FFound then - exit; - AcceptVisitor(AStmt.Stmt, self); -end; - procedure TBlockReplacerVisitor.Visit(const AStmt: IBlockStmt); begin - if FFound then - exit; - if not assigned(AStmt.Container) then - exit; AcceptVisitor(AStmt.Container, self); if FEvalVisitor.EvalExprAsString(AStmt.Name) = FBlockName then begin - FFound := true; AStmt.Container := FReplacementBlock; end; end; -procedure TBlockReplacerVisitor.Visit(const AStmt: IExtendsStmt); +procedure TBlockReplacerVisitor.Visit(const AStmt: IIncludeStmt); +var + LTemplate: ITemplate; begin - if FFound then - exit; - AcceptVisitor(AStmt.Container, self); + LTemplate := FEvalVisitor.ResolveTemplate(AStmt.Expr); + AcceptVisitor(LTemplate, self); end; constructor TBlockReplacerVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor); @@ -168,7 +96,6 @@ procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlock var LVisitor: ITemplateVisitor; begin - FFound := false; if not supports(self, ITemplateVisitor, LVisitor) then exit; if not assigned(ABlock) then @@ -179,4 +106,12 @@ procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlock FReplacementBlock := nil; end; +procedure TBlockReplacerVisitor.Visit(const AStmt: IExtendsStmt); +var + LTemplate: ITemplate; +begin + LTemplate := FEvalVisitor.ResolveTemplate(AStmt.Name); + AcceptVisitor(LTemplate, self); +end; + end. diff --git a/src/Sempare.Template.BlockResolver.pas b/src/Sempare.Template.BlockResolver.pas index 8781dad..ba52bcd 100644 --- a/src/Sempare.Template.BlockResolver.pas +++ b/src/Sempare.Template.BlockResolver.pas @@ -48,7 +48,7 @@ interface function GetBlocks(const AName: string): TArray; end; - TBlockResolverVisitor = class(TBaseTemplateVisitor, IBlockResolverVisitor) + TBlockResolverVisitor = class(TNoExprTemplateVisitor, IBlockResolverVisitor) private FBlocks: TDictionary>; FEvalVisitor: IEvaluationTemplateVisitor; @@ -59,70 +59,15 @@ TBlockResolverVisitor = class(TBaseTemplateVisitor, IBlockResolverVisitor) function GetBlockNames: TArray; function GetBlocks(const AName: string): TArray; - procedure Visit(const AStmt: IIfStmt); overload; override; - procedure Visit(const AStmt: IWhileStmt); overload; override; - procedure Visit(const AStmt: IForInStmt); overload; override; - procedure Visit(const AStmt: IForRangeStmt); overload; override; - - procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; - procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; - procedure Visit(const AStmt: IWithStmt); overload; override; - - procedure Visit(const AStmt: IDebugStmt); overload; override; - procedure Visit(const AStmt: IBlockStmt); overload; override; procedure Visit(const AStmt: IExtendsStmt); overload; override; - + procedure Visit(const AStmt: IIncludeStmt); overload; override; end; implementation { TBlockResolverVisitor } -procedure TBlockResolverVisitor.Visit(const AStmt: IForRangeStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IProcessTemplateStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IDefineTemplateStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IWithStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IForInStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IIfStmt); -begin - AcceptVisitor(AStmt.TrueContainer, self); - if AStmt.FalseContainer <> nil then - begin - AcceptVisitor(AStmt.FalseContainer, self); - end; -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IWhileStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TBlockResolverVisitor.Visit(const AStmt: IDebugStmt); -begin - AcceptVisitor(AStmt.Stmt, self); -end; - procedure TBlockResolverVisitor.Visit(const AStmt: IBlockStmt); var LList: TArray; @@ -138,8 +83,19 @@ procedure TBlockResolverVisitor.Visit(const AStmt: IBlockStmt); end; procedure TBlockResolverVisitor.Visit(const AStmt: IExtendsStmt); +var + LTemplate: ITemplate; +begin + LTemplate := FEvalVisitor.ResolveTemplate(AStmt.Name); + AcceptVisitor(LTemplate, self); +end; + +procedure TBlockResolverVisitor.Visit(const AStmt: IIncludeStmt); +var + LTemplate: ITemplate; begin - AcceptVisitor(AStmt.Container, self); + LTemplate := FEvalVisitor.ResolveTemplate(AStmt.Expr); + AcceptVisitor(LTemplate, self); end; constructor TBlockResolverVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor; const ATemplate: ITemplate); diff --git a/src/Sempare.Template.VariableExtraction.pas b/src/Sempare.Template.VariableExtraction.pas index a8dc7bf..baa3c97 100644 --- a/src/Sempare.Template.VariableExtraction.pas +++ b/src/Sempare.Template.VariableExtraction.pas @@ -60,32 +60,10 @@ TTemplateReferenceExtractionVisitor = class(TBaseTemplateVisitor) public constructor Create; destructor Destroy; override; - procedure Visit(const AExpr: IBinopExpr); overload; override; - procedure Visit(const AExpr: IUnaryExpr); overload; override; procedure Visit(const AExpr: IVariableExpr); overload; override; - procedure Visit(const AExpr: IVariableDerefExpr); overload; override; - procedure Visit(const AExprList: IExprList); overload; override; - procedure Visit(const AExpr: IEncodeExpr); overload; override; - procedure Visit(const AExpr: ITernaryExpr); overload; override; - procedure Visit(const AExpr: IArrayExpr); overload; override; procedure Visit(const AStmt: IAssignStmt); overload; override; - procedure Visit(const AStmt: IIncludeStmt); overload; override; - procedure Visit(const AStmt: IRequireStmt); overload; override; - procedure Visit(const AStmt: IPrintStmt); overload; override; - procedure Visit(const AStmt: IIfStmt); overload; override; - procedure Visit(const AStmt: IWhileStmt); overload; override; - procedure Visit(const AStmt: IForInStmt); overload; override; - procedure Visit(const AStmt: IForRangeStmt); overload; override; procedure Visit(const AStmt: IFunctionCallExpr); overload; override; - procedure Visit(const AStmt: IMethodCallExpr); overload; override; - - procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; - procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; - procedure Visit(const AStmt: IWithStmt); overload; override; - - procedure Visit(const AStmt: ICycleStmt); overload; override; - procedure Visit(const AStmt: IDebugStmt); overload; override; procedure Visit(const AStmt: IBlockStmt); overload; override; procedure Visit(const AStmt: IExtendsStmt); overload; override; @@ -123,23 +101,6 @@ function TTemplateReferenceExtractionVisitor.GetVariables: TArray; exit(FVariables.ToArray); end; -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: ITernaryExpr); -begin - AcceptVisitor(AExpr.Condition, self); - AcceptVisitor(AExpr.TrueExpr, self); - AcceptVisitor(AExpr.FalseExpr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IArrayExpr); -var - LIdx: integer; -begin - for LIdx := 0 to AExpr.ExprList.Count - 1 do - begin - AcceptVisitor(AExpr.ExprList.Expr[LIdx], self); - end; -end; - procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IAssignStmt); begin if not FLocalVariables.contains(AStmt.Variable) then @@ -147,71 +108,6 @@ procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IAssignStmt); AcceptVisitor(AStmt.Expr, self); end; -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IEncodeExpr); -begin - AcceptVisitor(AExpr.Expr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IBinopExpr); -begin - AcceptVisitor(AExpr.LeftExpr, self); - AcceptVisitor(AExpr.RightExpr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IUnaryExpr); -begin - AcceptVisitor(AExpr.Condition, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IVariableDerefExpr); -begin - AcceptVisitor(AExpr.Variable, self); - AcceptVisitor(AExpr.DerefExpr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AExprList: IExprList); -var - i: integer; -begin - for i := 0 to AExprList.Count - 1 do - begin - AcceptVisitor(AExprList[i], self); - end; -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IForRangeStmt); -begin - if not FLocalVariables.contains(AStmt.Variable) then - FLocalVariables.Add(AStmt.Variable); - - AcceptVisitor(AStmt.LowExpr, self); - AcceptVisitor(AStmt.HighExpr, self); - AcceptVisitor(AStmt.Container, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IMethodCallExpr); -begin - Visit(AStmt.ObjectExpr); - Visit(AStmt.ExprList); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IProcessTemplateStmt); -begin - AcceptVisitor(AStmt.Container, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IDefineTemplateStmt); -begin - AcceptVisitor(AStmt.Name, self); - AcceptVisitor(AStmt.Container, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IWithStmt); -begin - AcceptVisitor(AStmt.Expr, self); - AcceptVisitor(AStmt.Container, self); -end; - procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IFunctionCallExpr); begin if not FFunctions.contains(AStmt.FunctionInfo[0].Name) then @@ -219,51 +115,6 @@ procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IFunctionCallEx Visit(AStmt.ExprList); end; -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IForInStmt); -begin - if not FLocalVariables.contains(AStmt.Variable) then - FLocalVariables.Add(AStmt.Variable); - - AcceptVisitor(AStmt.Expr, self); - AcceptVisitor(AStmt.Container, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IIncludeStmt); -begin - AcceptVisitor(AStmt.Expr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IRequireStmt); -var - LIdx: integer; -begin - for LIdx := 0 to AStmt.ExprList.Count - 1 do - begin - AcceptVisitor(AStmt.ExprList.Expr[LIdx], self); - end; -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IPrintStmt); -begin - AcceptVisitor(AStmt.Expr, self); -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IIfStmt); -begin - AcceptVisitor(AStmt.Condition, self); - AcceptVisitor(AStmt.TrueContainer, self); - if AStmt.FalseContainer <> nil then - begin - AcceptVisitor(AStmt.FalseContainer, self); - end; -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IWhileStmt); -begin - AcceptVisitor(AStmt.Condition, self); - AcceptVisitor(AStmt.Container, self); -end; - procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IVariableExpr); begin if FLocalVariables.contains(AExpr.Variable) then @@ -273,21 +124,6 @@ procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IVariableExpr); FVariables.Add(AExpr.Variable); end; -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: ICycleStmt); -var - LIdx: integer; -begin - for LIdx := 0 to AStmt.List.Count - 1 do - begin - AcceptVisitor(AStmt.List.Expr[LIdx], self); - end; -end; - -procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IDebugStmt); -begin - AcceptVisitor(AStmt.Stmt, self); -end; - procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IBlockStmt); begin AcceptVisitor(AStmt.Container, self); From 43089d72653c346a29f449f104d93c54cb58f44a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:42:43 +0100 Subject: [PATCH 016/138] rename IUnaryExpr.Condition to IUnaryExpr.Expr fix TPrettyPrintTemplateVisitor where % not escaped correctly as %% --- src/Sempare.Template.PrettyPrint.pas | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 3e5354e..b770d73 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -415,7 +415,7 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AExpr: IBinopExpr); procedure TPrettyPrintTemplateVisitor.Visit(const AExpr: IUnaryExpr); begin write('%s (', [UnaryToStr(AExpr.UnaryOp)]); - AcceptVisitor(AExpr.Condition, self); + AcceptVisitor(AExpr.Expr, self); write(')'); end; @@ -473,9 +473,9 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); begin tab(); - write('<%% define ('); + write('<%% template '); AcceptVisitor(AStmt.Name, self); - writeln(') %%>'); + writeln(' %%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); @@ -486,14 +486,14 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IWithStmt); begin tab(); - write('<% with '); + write('<%% with '); AcceptVisitor(AStmt.Expr, self); - writeln(' %>'); + writeln(' %%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); tab(); - writeln('<% end %>'); + writeln('<%% end %%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IRequireStmt); @@ -501,14 +501,14 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IRequireStmt); LIdx: integer; begin tab(); - write('<% require('); + write('<%% require('); for LIdx := 0 to AStmt.ExprList.Count - 1 do begin if LIdx > 0 then write(','); AcceptVisitor(AStmt.ExprList.Expr[LIdx], self); end; - writeln('%>'); + writeln('%%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AExpr: IArrayExpr); @@ -539,14 +539,14 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: ICycleStmt); LIdx: integer; begin tab(); - write('<% cycle ['); + write('<%% cycle ['); for LIdx := 0 to AStmt.List.Count - 1 do begin if LIdx > 0 then write(','); AcceptVisitor(AStmt.List.Expr[LIdx], self); end; - writeln('%>'); + writeln('%%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDebugStmt); @@ -558,27 +558,27 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDebugStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IBlockStmt); begin tab(); - write('<% block '''); + write('<%% block '''); AcceptVisitor(AStmt.Name, self); - writeln('''%>'); + writeln('''%%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); tab(); - writeln('<% end %>'); + writeln('<%% end %%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IExtendsStmt); begin tab(); - write('<% extends '''); + write('<%% extends '''); AcceptVisitor(AStmt.Name, self); - writeln('''%>'); + writeln('''%%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); tab(); - writeln('<% end %>'); + writeln('<%% end %%>'); end; initialization From fbd2eaa1fa25152bb266de8e3523aa7f89a56c70 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:43:34 +0100 Subject: [PATCH 017/138] rename IUnaryExpr.Condition to IUnaryExpr.Expr add IEvaluationTemplateVisitor.ResolveTemplate --- src/Sempare.Template.AST.pas | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index dda61cb..5025143 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -480,7 +480,7 @@ ETemplate = class(Exception); function GetUnaryOp: TUnaryOp; function GetExpr: IExpr; property UnaryOp: TUnaryOp read GetUnaryOp; - property Condition: IExpr read GetExpr; + property Expr: IExpr read GetExpr; end; ITemplateVisitor = interface @@ -527,6 +527,7 @@ ETemplate = class(Exception); function EvalExprAsInt(const AExpr: IExpr): int64; function EvalExprAsNum(const AExpr: IExpr): extended; function EvalExprAsBoolean(const AExpr: IExpr): boolean; + function ResolveTemplate(const AExpr: IExpr): ITemplate; procedure VisitStmt(const AStmt: IStmt); end; From e45400e3dc5ef98881716bb9e6a73bb383d0746a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:44:23 +0100 Subject: [PATCH 018/138] refactor TEvaluationTemplateVisitor --- src/Sempare.Template.Evaluate.pas | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 0833166..5f122ca 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -103,6 +103,8 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor, IEvaluationTemplateVi function EvalExprAsBoolean(const AExpr: IExpr): boolean; procedure VisitStmt(const AStmt: IStmt); procedure VisitContainer(const AContainer: ITemplate); + function ResolveTemplate(const AExpr: IExpr): ITemplate; overload; + function ResolveTemplate(const APosition: IPosition; const AName: string): ITemplate; overload; public constructor Create(const AContext: ITemplateContext; const AValue: TValue; const AStream: TStream); overload; constructor Create(const AContext: ITemplateContext; const AStackFrame: TStackFrame; const AStream: TStream); overload; @@ -309,7 +311,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IUnaryExpr); var LValue: TValue; begin - LValue := EvalExpr(AExpr.Condition); + LValue := EvalExpr(AExpr.Expr); case AExpr.UnaryOp of uoMinus: begin @@ -891,7 +893,8 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIncludeStmt); if HasBreakOrContinue then exit; LTemplateName := EvalExprAsString(AStmt.Expr); - if FLocalTemplates.TryGetValue(LTemplateName, LTemplate) or FContext.TryGetTemplate(LTemplateName, LTemplate) then + LTemplate := ResolveTemplate(Position(AStmt.Expr), LTemplateName); + if assigned(LTemplate) then begin FStackFrames.push(FStackFrames.peek.Clone()); try @@ -899,9 +902,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIncludeStmt); finally FStackFrames.pop; end; - end - else - RaiseErrorRes(Position(AStmt), @STemplateNotFound, [LTemplateName]); + end; end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IPrintStmt); @@ -1025,6 +1026,19 @@ function TEvaluationTemplateVisitor.Invoke(const AExpr: IMethodCallExpr; const A exit(AExpr.RttiMethod.Invoke(AObject, AArgs)); end; +function TEvaluationTemplateVisitor.ResolveTemplate(const APosition: IPosition; const AName: string): ITemplate; +begin + if not FLocalTemplates.TryGetValue(AName, result) and not FContext.TryGetTemplate(AName, result) then + begin + RaiseErrorRes(APosition, @STemplateNotFound, [AName]); + end; +end; + +function TEvaluationTemplateVisitor.ResolveTemplate(const AExpr: IExpr): ITemplate; +begin + exit(ResolveTemplate(Position(AExpr), EvalExprAsString(AExpr))); +end; + procedure TEvaluationTemplateVisitor.VisitStmt(const AStmt: IStmt); begin AcceptVisitor(AStmt, self); @@ -1194,8 +1208,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); if not assigned(AStmt.Container) then begin LName := AStmt.NameAsString(self); - if not FLocalTemplates.TryGetValue(LName, LTemplate) and not FContext.TryGetTemplate(LName, LTemplate) then - RaiseErrorRes(LSymbol.Position, @STemplateNotFound, [LName]); + LTemplate := ResolveTemplate(Position(AStmt), LName); AStmt.Container := LTemplate; LResolveNames := true; From 615e3fb9021d2c3ce76643631dbbb4954c159381 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 15:45:04 +0100 Subject: [PATCH 019/138] add more tests to TTestTemplateInclude --- tests/Sempare.Template.TestInclude.pas | 141 ++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index 6ccb3e6..d4ae17e 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -78,11 +78,18 @@ TTestTemplateInclude = class [Test] procedure TestExtendsScopedExpr; + [Test] + procedure TestWebForm; + + [Test] + procedure TestNestedBody; + end; implementation uses + System.SysUtils, System.Generics.Collections, Sempare.Template.Context, Sempare.Template; @@ -368,7 +375,7 @@ procedure TTestTemplateInclude.TestExtendsNested; '<% end %>' // )); - Assert.AreEqual('header default header general footer default footer', Template.Eval( // + Assert.AreEqual('header general general footer general', Template.Eval( // '<% template ''header'' %>' + // 'header <% block ''general'' %>default header<% end %>' + // '<% end %>' + // @@ -401,6 +408,138 @@ procedure TTestTemplateInclude.TestExtendsScopedExpr; )); end; +type + TField = record + Caption: string; + Name: string; + FieldType: string; + constructor create(const ACaption, AName: string; const AFieldType: string = 'TEdit'); + end; + + TButton = record + Caption: string; + Name: string; + constructor create(const ACaption, AName: string); + end; + +constructor TField.create(const ACaption, AName, AFieldType: string); +begin + Caption := ACaption; + name := AName; + FieldType := AFieldType; +end; + +constructor TButton.create(const ACaption, AName: string); +begin + Caption := ACaption; + name := AName; +end; + +procedure TTestTemplateInclude.TestWebForm; + +type + + TTemplateData = record + company: string; + CopyrightYear: integer; + FormName: string; + FormAction: string; + Fields: TArray; + Buttons: TArray; + end; + +var + LTemplateData: TTemplateData; + LResult: string; +begin + LTemplateData.company := 'Sempare'; + LTemplateData.CopyrightYear := 2023; + LTemplateData.FormName := 'userinfo'; + LTemplateData.FormAction := '/userinfo'; + LTemplateData.Fields := [TField.create('FirstName', 'firstname'), TField.create('LastName', 'lastname'), TField.create('Email', 'email', 'TEmail')]; + LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; + + LResult := Template.Eval( // + '<% template "TEdit" %><% Caption %><% end %>'#13#10 + // 1 + + '<% template "TEmail" %><% Caption %><% end %>'#13#10 + // 2 + + '<% template "TButton" %><% end %>'#13#10 + // 3 + + '<% template "TForm" %>'#13#10 + // 4 + '
'#13#10 + // 5 + ' '#13#10 + // 6 + ' <% for field of fields %>'#13#10 + // 7 + ' <% include(field.FieldType, field)%>'#13#10 + // 8 + ' <% end %>'#13#10 + // 9 + ' '#13#10 + // + ' '#13#10 + // 14 + ' '#13#10 + // 14 + '
'#13#10 + // 10 + ' <% for button of buttons %>'#13#10 + // 11 + ' <% include("TButton", button) %>'#13#10 + // 12 + ' <% end %>'#13#10 + // 13 + '
'#13#10 + // 15 + '
'#13#10 + // 16 + '<% end %>'#13#10 + // 17 + + '<% template "header" %>'#13#10 + // 18 + ''#13#10 + // 19 + ' '#13#10 + // 20 + ' Welcome to my <% Company %>'#13#10 + // 21 + ' '#13#10 + // 22 + ' '#13#10 + // 23 + '<% end %>'#13#10 + // 24 + + '<% template "footer" %>'#13#10 + // 25 + '

Copyright (c) <% CopyrightYear %>

'#13#10 + // 26 + ' '#13#10 + // 27 + ''#13#10 + // 28 + '<% end %>'#13#10 + // 29 + + '<% template "template" %>'#13#10 + // 30 + '<% include("header") %>'#13#10 + // 31 + '<% block "body" %>Lorem ipsum dolor sit amet, consectetur adipiscing eli...<% end %>'#13#10 + // 32 + '<% include("footer") %>'#13#10 + // 33 + '<% end %>'#13#10 + // 34 + + '<% extends ("template") %>' + // 35 + '<% block "body" %><% include("TForm") %><% end %> '#13#10 + // 36 + '<% end %>'#13#10 // 37 + , LTemplateData); + + // LResult := LResult.Replace(#13#10, '''#13#10''', [rfReplaceAll]); + + Assert.AreEqual(#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10''#13#10' '#13#10' Welcome to my Sempare'#13#10 + // + ' '#13#10' '#13#10''#13#10''#13#10'
'#13#10 + // + ' '#13#10' '#13#10' '#13#10' '#13#10' '#13#10 + // + ' '#13#10' '#13#10' '#13#10 + // + ' '#13#10' '#13#10' '#13#10 + // + '
FirstName' + // + '
LastName
Email
'#13#10' '#13#10 + // + ' '#13#10' '#13#10'
'#13#10'
'#13#10''#13#10''#13#10'

Copyright (c) 2023

'#13#10' '#13#10''#13#10''#13#10''#13#10, LResult); +end; + +procedure TTestTemplateInclude.TestNestedBody; +begin + Assert.AreEqual('hellohello', Template.Eval( // + '<% template "tpl1" %>' + // + '<% block "content" %>tpl1<% end %>' + // + '<% end %>' + // + + '<% template "template" %>' + // + '<% include("tpl1") %>' + // + '<% block "content" %>tpl1<% end %>' + // + '<% end %>' + // + + '<% extends ("template") %>' + // + ' // this is ignored ' + // + '<% block "content" %>hello<% end %>' + // + ' // this is ignored ' + // + '<% end %>' + // + '')); +end; + initialization TDUnitX.RegisterTestFixture(TTestTemplateInclude); From c3440367557793d57840295be9d25e5d79c3545c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 17:36:34 +0100 Subject: [PATCH 020/138] Refactor --- src/Sempare.Template.Common.pas | 15 +-- src/Sempare.Template.Parser.pas | 122 +------------------------ src/Sempare.Template.pas | 10 +- tests/Sempare.Template.TestInclude.pas | 32 +++++++ 4 files changed, 45 insertions(+), 134 deletions(-) diff --git a/src/Sempare.Template.Common.pas b/src/Sempare.Template.Common.pas index 4513305..658f449 100644 --- a/src/Sempare.Template.Common.pas +++ b/src/Sempare.Template.Common.pas @@ -122,27 +122,18 @@ function CloneStmt(const AStmt: IStmt): IStmt; implementation function CloneTemplate(const ATemplate: ITemplate): ITemplate; -var - LIntf: IInterface; begin - LIntf := ATemplate.Clone; - supports(LIntf, ITemplate, result); + supports(ATemplate.Clone, ITemplate, result); end; function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; -var - LIntf: IInterface; begin - LIntf := AVisitorHost.Clone; - supports(LIntf, ITemplateVisitorHost, result); + supports(AVisitorHost.Clone, ITemplateVisitorHost, result); end; function CloneStmt(const AStmt: IStmt): IStmt; -var - LIntf: IInterface; begin - LIntf := AStmt.Clone; - supports(LIntf, IStmt, result); + supports(AStmt.Clone, IStmt, result); end; function AsVisitorHost(const ATemplate: ITemplate): ITemplateVisitorHost; overload; diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 075a802..b1389ff 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -91,7 +91,7 @@ TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisito constructor Create(const APosition: IPosition); destructor Destroy; override; procedure Accept(const AVisitor: ITemplateVisitor); virtual; abstract; - function Clone: IInterface; virtual; abstract; + function Clone: IInterface; virtual; end; TAbstractStmt = class abstract(TAbstractBase, IStmt) @@ -104,19 +104,17 @@ TDebugStmt = class(TAbstractStmt, IDebugStmt) public constructor Create(const AStmt: IStmt); procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TEndStmt = class(TAbstractStmt, IEndStmt) public procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TExtendsStmt = class(TAbstractStmt, IExtendsStmt) private FName: IExpr; - FBlockContainer: ITemplate; // NOTE: FContainer is something that is resolved + FBlockContainer: ITemplate; FContainer: ITemplate; // NOTE: FContainer is something that is resolved FBlockNames: TArray; // NOTE: names in the container function GetName: IExpr; @@ -149,31 +147,26 @@ TBlockStmt = class(TAbstractStmt, IBlockStmt) TElseStmt = class(TAbstractStmt, IElseStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TContinueStmt = class(TAbstractStmt, IContinueStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TBreakStmt = class(TAbstractStmt, IBreakStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TCommentStmt = class(TAbstractStmt, ICommentStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TElIfStmt = class(TAbstractStmt, IElIfStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TAbstractStmtWithExpr = class abstract(TAbstractStmt) @@ -187,13 +180,11 @@ TAbstractStmtWithExpr = class abstract(TAbstractStmt) TPrintStmt = class(TAbstractStmtWithExpr, IPrintStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TIncludeStmt = class(TAbstractStmtWithExpr, IIncludeStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; end; TRequireStmt = class(TAbstractStmt, IRequireStmt) @@ -201,7 +192,6 @@ TRequireStmt = class(TAbstractStmt, IRequireStmt) FExprList: IExprList; function GetExprList: IExprList; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AExprList: IExprList); end; @@ -215,7 +205,6 @@ TIfStmt = class(TAbstractStmt, IIfStmt) function GetTrueContainer: ITemplate; function GetFalseContainer: ITemplate; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const ACondition: IExpr; const ATrueContainer: ITemplate; const AFalseContainer: ITemplate); end; @@ -234,7 +223,6 @@ TProcessTemplateStmt = class(TAbstractStmtWithContainer, IProcessTemplateStmt) function GetAllowNewLine: boolean; procedure SetAllowNewLine(const AAllow: boolean); procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AContainer: ITemplate; const AAllowNewLine: boolean = true); end; @@ -244,7 +232,6 @@ TDefineTemplateStmt = class(TAbstractStmtWithContainer, IDefineTemplateStmt) FName: IExpr; function GetName: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); end; @@ -254,7 +241,6 @@ TWithStmt = class(TAbstractStmtWithContainer, IWithStmt) FExpr: IExpr; function GetExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AExpr: IExpr; const AContainer: ITemplate); end; @@ -283,7 +269,6 @@ TWhileStmt = class(TLoopStmt, IWhileStmt) procedure Accept(const AVisitor: ITemplateVisitor); override; function GetOffsetExpr: IExpr; function GetLimitExpr: IExpr; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const ACondition: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -301,7 +286,6 @@ TForInStmt = class(TLoopStmt, IForInStmt) function GetOffsetExpr: IExpr; function GetLimitExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const AExpr: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -319,7 +303,6 @@ TForRangeStmt = class(TLoopStmt, IForRangeStmt) function GetHighExpr: IExpr; function GetStepExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const ALowExpr: IExpr; const AHighExpr: IExpr; const AStep: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -329,7 +312,6 @@ TAssignStmt = class(TAbstractStmtWithExpr, IAssignStmt) FVariable: string; function GetVariable: string; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AVariable: string; const AExpr: IExpr); end; @@ -338,7 +320,6 @@ TCycleStmt = class(TAbstractStmt, ICycleStmt) private FExprList: IExprList; procedure Accept(const AVisitor: ITemplateVisitor); override; - function Clone: IInterface; override; public constructor Create(const APosition: IPosition; const AList: IExprList); function GetList: IExprList; @@ -364,7 +345,6 @@ TValueExpr = class(TAbstractExpr, IValueExpr) FValue: TValue; function GetValue: TValue; procedure Accept(const AVisitor: ITemplateVisitor); override; - public constructor Create(const APosition: IPosition; const AValue: TValue); end; @@ -1877,11 +1857,6 @@ procedure TIfStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TIfStmt.Clone: IInterface; -begin - exit(TIfStmt.Create(FPosition, FCondition, CloneTemplate(FTrueContainer), CloneTemplate(FFalseContainer))); -end; - constructor TIfStmt.Create(const APosition: IPosition; const ACondition: IExpr; const ATrueContainer: ITemplate; const AFalseContainer: ITemplate); begin inherited Create(APosition); @@ -1949,11 +1924,6 @@ procedure TPrintStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TPrintStmt.Clone: IInterface; -begin - exit(TPrintStmt.Create(FPosition, FExpr)); -end; - { TForInStmt } procedure TForInStmt.Accept(const AVisitor: ITemplateVisitor); @@ -1961,11 +1931,6 @@ procedure TForInStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TForInStmt.Clone: IInterface; -begin - exit(TForInStmt.Create(FPosition, FVariable, FForOp, FExpr, FOffsetExpr, FLimitExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); -end; - constructor TForInStmt.Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const AExpr: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -2008,11 +1973,6 @@ procedure TForRangeStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TForRangeStmt.Clone: IInterface; -begin - exit(TForRangeStmt.Create(FPosition, FVariable, FForOp, FLowExpr, FHighExpr, FStepExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); -end; - constructor TForRangeStmt.Create(const APosition: IPosition; const AVariable: string; const AForOp: TForOp; const ALowExpr: IExpr; const AHighExpr: IExpr; const AStep: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -2055,11 +2015,6 @@ procedure TAssignStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TAssignStmt.Clone: IInterface; -begin - exit(self); -end; - constructor TAssignStmt.Create(const APosition: IPosition; const AVariable: string; const AExpr: IExpr); begin inherited Create(APosition, AExpr); @@ -2130,11 +2085,6 @@ procedure TWhileStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TWhileStmt.Clone: IInterface; -begin - exit(TWhileStmt.Create(FPosition, FCondition, FOffsetExpr, FLimitExpr, CloneTemplate(FContainer), CloneTemplate(FOnFirst), CloneTemplate(FOnLast), CloneTemplate(FOnEmpty), CloneTemplate(FBetweenItem))); -end; - constructor TWhileStmt.Create(const APosition: IPosition; const ACondition: IExpr; const AOffsetExpr: IExpr; const ALimitExpr: IExpr; const AContainer: ITemplate; const AOnFirst, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); begin inherited Create(APosition, AContainer, AOnFirst, AOnEnd, AOnEmpty, ABetweenItem); @@ -2165,11 +2115,6 @@ procedure TContinueStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TContinueStmt.Clone: IInterface; -begin - exit(self); -end; - { TBreakStmt } procedure TBreakStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2177,11 +2122,6 @@ procedure TBreakStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TBreakStmt.Clone: IInterface; -begin - exit(self); -end; - { TEndStmt } procedure TEndStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2189,11 +2129,6 @@ procedure TEndStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TEndStmt.Clone: IInterface; -begin - exit(self); -end; - { TVariableDerefExpr } procedure TVariableDerefExpr.Accept(const AVisitor: ITemplateVisitor); @@ -2225,11 +2160,6 @@ procedure TIncludeStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TIncludeStmt.Clone: IInterface; -begin - exit(self); -end; - { TElseStmt } procedure TElseStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2237,37 +2167,25 @@ procedure TElseStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TElseStmt.Clone: IInterface; -begin - exit(self); -end; - { TElIfStmt } procedure TElIfStmt.Accept(const AVisitor: ITemplateVisitor); begin - // AVisitor.Visit(self); // this is on purpose -end; - -function TElIfStmt.Clone: IInterface; -begin - exit(self); end; { TCommentStmt } procedure TCommentStmt.Accept(const AVisitor: ITemplateVisitor); begin - // AVisitor.Visit(self); // this is on purpose end; -function TCommentStmt.Clone: IInterface; +{ TAbstractBase } + +function TAbstractBase.Clone: IInterface; begin exit(self); end; -{ TAbstractBase } - constructor TAbstractBase.Create(const APosition: IPosition); begin FPosition := APosition; @@ -2332,11 +2250,6 @@ procedure TProcessTemplateStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TProcessTemplateStmt.Clone: IInterface; -begin - exit(TProcessTemplateStmt.Create(FPosition, CloneTemplate(FContainer), FAllowNewline)); -end; - constructor TProcessTemplateStmt.Create(const APosition: IPosition; const AContainer: ITemplate; const AAllowNewLine: boolean); begin inherited Create(APosition, AContainer); @@ -2360,11 +2273,6 @@ procedure TDefineTemplateStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TDefineTemplateStmt.Clone: IInterface; -begin - exit(TDefineTemplateStmt.Create(FPosition, FName, CloneTemplate(FContainer))); -end; - constructor TDefineTemplateStmt.Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); begin inherited Create(APosition, AContainer); @@ -2383,11 +2291,6 @@ procedure TWithStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TWithStmt.Clone: IInterface; -begin - exit(TWithStmt.Create(FPosition, FExpr, CloneTemplate(FContainer))); -end; - constructor TWithStmt.Create(const APosition: IPosition; const AExpr: IExpr; const AContainer: ITemplate); begin inherited Create(APosition, AContainer); @@ -2460,11 +2363,6 @@ procedure TRequireStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TRequireStmt.Clone: IInterface; -begin - exit(self); -end; - constructor TRequireStmt.Create(const APosition: IPosition; const AExprList: IExprList); begin inherited Create(APosition); @@ -2535,11 +2433,6 @@ procedure TCycleStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TCycleStmt.Clone: IInterface; -begin - exit(self); -end; - constructor TCycleStmt.Create(const APosition: IPosition; const AList: IExprList); begin inherited Create(APosition); @@ -2558,11 +2451,6 @@ procedure TDebugStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -function TDebugStmt.Clone: IInterface; -begin - exit(TDebugStmt.Create(CloneStmt(FStmt))); -end; - constructor TDebugStmt.Create(const AStmt: IStmt); begin FStmt := AStmt; diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index 3f06a5f..a31e0bd 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -169,7 +169,8 @@ class procedure Template.Eval(const ATemplate: ITemplate; const AValue: T; co class procedure Template.Eval(const AContext: ITemplateContext; const ATemplate: ITemplate; const AValue: T; const AStream: TStream); var LValue: TTemplateValue; - LTemplateVisitor: ITemplateVisitor; + LTemplateVisitor: IEvaluationTemplateVisitor; + LTemplate: ITemplate; begin {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} {$IFDEF MSWINDOWS} @@ -179,17 +180,16 @@ class procedure Template.Eval(const AContext: ITemplateContext; const ATempla 'Thank you for trying the Sempare Template Engine.'#13#10#13#10 + // 'To supress this message, set the conditional define SEMPARE_TEMPLATE_CONFIRM_LICENSE in the project options.'#13#10#13#10 + // 'Please remember the library is dual licensed. You are free to use it under the GPL or you can support the project to keep it alive as per:'#13#10#13#10 + // - 'https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md' // + 'https://github.com/sempare/sempare-delphi-template-engine/blob/main/docs/commercial.license.md' // ); FLicenseShown := true; end; {$ENDIF} {$ENDIF} + LTemplate := CloneTemplate(ATemplate); LValue := TTemplateValue.From(AValue); - if typeinfo(T) = typeinfo(TTemplateValue) then - LValue := LValue.AsType(); LTemplateVisitor := TEvaluationTemplateVisitor.Create(AContext, LValue, AStream); - AcceptVisitor(ATemplate, LTemplateVisitor); + AcceptVisitor(LTemplate, LTemplateVisitor); end; class procedure Template.Eval(const ATemplate: ITemplate; const AStream: TStream; const AOptions: TTemplateEvaluationOptions); diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index d4ae17e..383d9f9 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -84,6 +84,9 @@ TTestTemplateInclude = class [Test] procedure TestNestedBody; + [Test] + procedure TestNestedBody2; + end; implementation @@ -540,6 +543,35 @@ procedure TTestTemplateInclude.TestNestedBody; '')); end; +procedure TTestTemplateInclude.TestNestedBody2; +var + LTemplate: ITemplate; +begin + LTemplate := Template.parse( // + '<% template "tpl1" %>' + // + ' <% block "content" %>tpl1<% end %>' + // + '<% end %>' + // + + '<% template "template" %>' + // + ' <% extends ("tpl1") %>' + // + ' // this is ignored ' + // + ' <% block "content" %><% _ %><% end %>' + // + ' // this is ignored ' + // + ' <% end %>' + // + ' <% block "content" %>tpl1<% end %>' + // + '<% end %>' + // + + '<% extends ("template") %>' + // + ' // this is ignored ' + // + ' <% block "content" %>hello<% end %>' + // + ' // this is ignored ' + // + '<% end %>' + // + ''); + + Assert.AreEqual(' hello123 hello', Template.Eval(LTemplate, 'hello123')); + Assert.AreEqual(' hello456 hello', Template.Eval(LTemplate, 'hello456')); +end; + initialization TDUnitX.RegisterTestFixture(TTestTemplateInclude); From 0a8a8fa9d453d639538e38e7a7d8c3e6478775d5 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 17:50:54 +0100 Subject: [PATCH 021/138] Clarify docs --- README.md | 10 +++++----- docs/commercial.license.md | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 04663f2..820240f 100644 --- a/README.md +++ b/README.md @@ -189,15 +189,15 @@ Review [contibution terms and conditions](./docs/CONTRIBUTION.pdf) to contribute # License The Sempare Template Engine is dual-licensed. You may choose to use it under the restrictions of the [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) at -no cost to you, or you may purchase for use under the [Sempare Limited Commercial License](./docs/commercial.license.md) +no cost to you, or you may license it for use under the [Sempare Limited Commercial License](./docs/commercial.license.md) -The dual-licensing scheme allows you to test the library with no restrictions, but subject to the terms of the GPL. A nominal fee is requested to support the maintenance of the library if the product is to be used in commercial products. This support fee binds you to the commercial license, removing any of the GPL restrictions, and allowing you to use the library in your products as you will. The Sempare Template Engine may not be included/distributed as part of another commercial library without approval / commercial review. +The dual-licensing scheme allows you to use and test the library with no restrictions, but subject to the terms of the GPL. A nominal fee is requested to support the maintenance of the library if the product is to be used in commercial products. This support fee binds you to the commercial license, removing any of the GPL restrictions, and allowing you to use the library in your products as you will. The Sempare Template Engine may NOT be included or distributed as part of another commercial library or framework without approval / commercial review. A commercial licence grants you the right to use Sempare Template Engine in your own applications, royalty free, and without any requirement to disclose your source code nor any modifications to -Sempare Templte Engine to any other party. A commercial licence lasts into perpetuity, and entitles you to all future updates - free of charge. +Sempare Templte Engine or to any other party. A commercial licence lasts into perpetuity, and entitles you to all future updates. -A commercial licence is provided per developer developing applications that use the Sempare Template Engine. The initial cost is $70 per developer and includes first year of support. -For support thereafter, at your discretion, a support fee of $30 per developer per year would be appreciated (the cost of a few cups of coffee). Please contact us for site license pricing. +A commercial licence is provided per developer developing applications that uses the Sempare Template Engine. The initial license fee is $70 per developer and includes the first year of support. +For support thereafter, at your discretion, a support fee of $30 per developer per year would be appreciated. Please contact us for site license pricing. Please send an e-mail to info@sempare.ltd to request an invoice which will contain the bank details. diff --git a/docs/commercial.license.md b/docs/commercial.license.md index edf0dd4..52c3c2d 100644 --- a/docs/commercial.license.md +++ b/docs/commercial.license.md @@ -8,12 +8,11 @@ The commercial license for Sempare Template Engine for Delphi gives you the righ - use the component and source code on all development systems used by the developer - sell any number of applications in any quantity without any additional run-time fees required -The Sempare Template Engine may not be included/distributed as part of another commercial library without approval / commercial review. +The Sempare Template Engine may NOT be included or distributed as part of another commercial library or framework without approval / commercial review. -A commercial licence is sold per developer developing applications that use Sempare Template Engine for Delphi. -The initial cost is $70 per developer and includes first year of support. For support thereafter, at your discretion, -a support fee of $30 per developer per year would be appreciated (the cost of a few cups of coffee). Please contact us -for site license pricing. +A commercial licence is licensed per developer developing applications that use Sempare Template Engine for Delphi. +The initial license fee is $70 per developer and includes first year of support. For support thereafter, at your discretion, +a support fee of $30 per developer per year would be appreciated. Please contact us for site license pricing. Please send an e-mail to info@sempare.ltd to request an invoice which will contain the bank details. From d7e5f546d779bce019e037343f86cbe0ea5a8889 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 21:43:59 +0100 Subject: [PATCH 022/138] add IStripStmt and ICompositeStmt --- src/Sempare.Template.AST.pas | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index d7cdf9e..f2ca98e 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -215,6 +215,24 @@ ETemplate = class(Exception); ['{FB4CC3AB-BFEC-4189-B555-153DDA490D15}'] end; + TStripDirection = (sdLeft, sdRight); + + IStripStmt = interface(IStmt) + ['{3313745B-D635-4453-9808-660DC462E15C}'] + function GetDirection: TStripDirection; + function GetAction: TStripAction; + property Direction: TStripDirection read GetDirection; + property Action: TStripAction read GetAction; + end; + + ICompositeStmt = interface(IStmt) + ['{790FB188-9763-401F-A0B1-FC9CCF4EF18D}'] + function GetFirstStmt: IStmt; + function GetSecondStmt: IStmt; + property FirstStmt: IStmt read GetFirstStmt; + property SecondStmt: IStmt read GetSecondStmt; + end; + IEndStmt = interface(IStmt) ['{926DED70-2F66-4810-9DF1-FCFD83FF7E5D}'] end; @@ -491,8 +509,19 @@ ETemplate = class(Exception); procedure Visit(const AStmt: IWithStmt); overload; procedure Visit(const AStmt: ICycleStmt); overload; procedure Visit(const AStmt: IDebugStmt); overload; + procedure Visit(const AStmt: ICompositeStmt); overload; + procedure Visit(const AStmt: IStripStmt); overload; end; +const + StripDirectionStr: array [TStripDirection] of string = ('sdLeft', 'sdRight'); + + StripActionStr: array [TStripAction] of string = ( // + 'saWhitespace', // + 'saWhitespaceAndNL', // + 'saNone' // + ); + implementation end. From 26f3b097bfbcfeceada216422248752152acc1e3 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 21:44:52 +0100 Subject: [PATCH 023/138] support ICompositeStmt and IStripStmt on visitors --- src/Sempare.Template.Evaluate.pas | 14 +++++++ src/Sempare.Template.PrettyPrint.pas | 43 +++++++++++++++++---- src/Sempare.Template.VariableExtraction.pas | 13 +++++++ src/Sempare.Template.Visitor.pas | 17 +++++++- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 06c30d0..b6ad898 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -132,6 +132,9 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IWithStmt); overload; override; procedure Visit(const AStmt: ICycleStmt); overload; override; procedure Visit(const AStmt: IDebugStmt); overload; override; + procedure Visit(const AStmt: ICompositeStmt); overload; override; + procedure Visit(const AStmt: IStripStmt); overload; override; + end; implementation @@ -1154,6 +1157,17 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDebugStmt); end; end; +procedure TEvaluationTemplateVisitor.Visit(const AStmt: IStripStmt); +begin + +end; + +procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICompositeStmt); +begin + AcceptVisitor(AStmt.FirstStmt, self); + AcceptVisitor(AStmt.SecondStmt, self); +end; + { TNewLineStreamWriter } constructor TNewLineStreamWriter.Create(const AStream: TStream; const AEncoding: TEncoding; const ANL: string; const AOptions: TTemplateEvaluationOptions); diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 2d282d3..4a4e9a1 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -84,6 +84,8 @@ TPrettyPrintTemplateVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; procedure Visit(const AStmt: IWithStmt); overload; override; procedure Visit(const AStmt: ICycleStmt); overload; override; + procedure Visit(const AStmt: ICompositeStmt); overload; override; + procedure Visit(const AStmt: IStripStmt); overload; override; end; @@ -482,14 +484,14 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IWithStmt); begin tab(); - write('<% with '); + write('<%% with '); AcceptVisitor(AStmt.Expr, self); - writeln(' %>'); + writeln(' %%>'); delta(4); AcceptVisitor(AStmt.Container, self); delta(-4); tab(); - writeln('<% end %>'); + writeln('<%% end %%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IRequireStmt); @@ -497,14 +499,14 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IRequireStmt); LIdx: integer; begin tab(); - write('<% require('); + write('<%% require('); for LIdx := 0 to AStmt.ExprList.Count - 1 do begin if LIdx > 0 then write(','); AcceptVisitor(AStmt.ExprList.Expr[LIdx], self); end; - writeln('%>'); + writeln('%%>'); end; procedure TPrettyPrintTemplateVisitor.Visit(const AExpr: IArrayExpr); @@ -535,14 +537,41 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: ICycleStmt); LIdx: integer; begin tab(); - write('<% cycle ['); + write('<%% cycle ('); for LIdx := 0 to AStmt.List.Count - 1 do begin if LIdx > 0 then write(','); AcceptVisitor(AStmt.List.Expr[LIdx], self); end; - writeln('%>'); + writeln(') %%>'); +end; + +procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IStripStmt); +var + LIdx: integer; +begin + tab(); + write('<%% strip('); + write(StripDirectionStr[AStmt.Direction]); + write(','); + write(StripActionStr[AStmt.Action]); + write(')'); + writeln('%%>'); +end; + +procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: ICompositeStmt); +var + LIdx: integer; +begin + tab(); + writeln('<%% composite %%>'); + delta(4); + AcceptVisitor(AStmt.FirstStmt, self); + AcceptVisitor(AStmt.SecondStmt, self); + delta(-4); + tab(); + writeln('<%% end %%>'); end; initialization diff --git a/src/Sempare.Template.VariableExtraction.pas b/src/Sempare.Template.VariableExtraction.pas index 69a0b4b..4f144b7 100644 --- a/src/Sempare.Template.VariableExtraction.pas +++ b/src/Sempare.Template.VariableExtraction.pas @@ -83,6 +83,8 @@ TTemplateReferenceExtractionVisitor = class(TBaseTemplateVisitor) procedure Visit(const AStmt: IProcessTemplateStmt); overload; override; procedure Visit(const AStmt: IDefineTemplateStmt); overload; override; procedure Visit(const AStmt: IWithStmt); overload; override; + procedure Visit(const AStmt: ICompositeStmt); overload; override; + procedure Visit(const AStmt: IStripStmt); overload; override; property Variables: TArray read GetVariables; property Functions: TArray read GetFunctions; @@ -267,4 +269,15 @@ procedure TTemplateReferenceExtractionVisitor.Visit(const AExpr: IVariableExpr); FVariables.Add(AExpr.Variable); end; +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: IStripStmt); +begin + +end; + +procedure TTemplateReferenceExtractionVisitor.Visit(const AStmt: ICompositeStmt); +begin + AcceptVisitor(AStmt.FirstStmt, self); + AcceptVisitor(AStmt.SecondStmt, self); +end; + end. diff --git a/src/Sempare.Template.Visitor.pas b/src/Sempare.Template.Visitor.pas index 15691bf..3027d8f 100644 --- a/src/Sempare.Template.Visitor.pas +++ b/src/Sempare.Template.Visitor.pas @@ -46,7 +46,6 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AContainer: ITemplate); overload; virtual; procedure Visit(const AContainer: ITemplateVisitorHost); overload; virtual; - procedure Visit(const AExpr: IExpr); overload; virtual; procedure Visit(const AExpr: IBinopExpr); overload; virtual; procedure Visit(const AExpr: IUnaryExpr); overload; virtual; procedure Visit(const AExpr: IVariableExpr); overload; virtual; @@ -55,8 +54,8 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AExprList: IExprList); overload; virtual; procedure Visit(const AExpr: ITernaryExpr); overload; virtual; procedure Visit(const AExpr: IArrayExpr); overload; virtual; + procedure Visit(const AExpr: IExpr); overload; virtual; - procedure Visit(const AStmt: IStmt); overload; virtual; procedure Visit(const AStmt: IAssignStmt); overload; virtual; procedure Visit(const AStmt: IContinueStmt); overload; virtual; procedure Visit(const AStmt: IElseStmt); overload; virtual; @@ -77,6 +76,10 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AStmt: IWithStmt); overload; virtual; procedure Visit(const AStmt: ICycleStmt); overload; virtual; procedure Visit(const AStmt: IDebugStmt); overload; virtual; + procedure Visit(const AStmt: ICompositeStmt); overload; virtual; + procedure Visit(const AStmt: IStripStmt); overload; virtual; + procedure Visit(const AStmt: IStmt); overload; virtual; + end; implementation @@ -253,4 +256,14 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IDebugStmt); end; +procedure TBaseTemplateVisitor.Visit(const AStmt: IStripStmt); +begin + +end; + +procedure TBaseTemplateVisitor.Visit(const AStmt: ICompositeStmt); +begin + +end; + end. From 48d97607af41012fa7a5b22abd689bf8a9f58c71 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 29 Mar 2023 21:46:17 +0100 Subject: [PATCH 024/138] add TStripStmt and TCompositeStmt --- src/Sempare.Template.Lexer.pas | 4 +- src/Sempare.Template.Parser.pas | 85 +++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index 57ec10c..304f0ca 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -728,8 +728,8 @@ initialization AddHashedKeyword('onempty', vsOnEmpty); AddHashedKeyword('betweenitems', vsBetweenItem); -AddSymKeyword('ScriptStartToken', VsStartScript); -AddSymKeyword('ScriptEndToken', VsEndScript); +AddSymKeyword('<%', VsStartScript); +AddSymKeyword('%>', VsEndScript); AddSymKeyword('(', vsOpenRoundBracket); AddSymKeyword(')', vsCloseRoundBracket); AddSymKeyword('(* *) ', vsComment); diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 184f860..8209954 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -146,6 +146,28 @@ TPrintStmt = class(TAbstractStmtWithExpr, IPrintStmt) procedure Accept(const AVisitor: ITemplateVisitor); override; end; + TStripStmt = class(TAbstractStmt, IStripStmt) + private + FDirection: TStripDirection; + FAction: TStripAction; + function GetDirection: TStripDirection; + function GetAction: TStripAction; + procedure Accept(const AVisitor: ITemplateVisitor); override; + public + constructor Create(const ADirection: TStripDirection; const AAction: TStripAction); + end; + + TCompositeStmt = class(TAbstractStmt, ICompositeStmt) + private + FFirstStmt: IStmt; + FSecondStmt: IStmt; + function GetFirstStmt: IStmt; + function GetSecondStmt: IStmt; + procedure Accept(const AVisitor: ITemplateVisitor); override; + public + constructor Create(const AFirstStmt, ASecondStmt: IStmt); + end; + TIncludeStmt = class(TAbstractStmtWithExpr, IIncludeStmt) private procedure Accept(const AVisitor: ITemplateVisitor); override; @@ -438,7 +460,7 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function lookaheadValue: string; function matchValue(const ASymbol: TTemplateSymbol): string; procedure match(ASymbol: ITemplateSymbol); overload; inline; - procedure match(const ASymbol: TTemplateSymbol); overload; + function match(const ASymbol: TTemplateSymbol): TStripAction; overload; function matchNumber(const ASymbol: TTemplateSymbol): extended; private @@ -864,10 +886,11 @@ function TTemplateParser.ruleTerm: IExpr; function TTemplateParser.ruleStmt: IStmt; var LSymbol: ITemplateSymbol; + LStripAction: TStripAction; begin result := nil; LSymbol := FLookahead; - match(vsStartScript); + LStripAction := match(vsStartScript); case FLookahead.Token of vsBreak: result := ruleBreakStmt; @@ -908,6 +931,11 @@ function TTemplateParser.ruleStmt: IStmt; begin result := TDebugStmt.Create(result); end; + + if LStripAction <> TStripAction.saNone then + begin + result := TCompositeStmt.Create(TStripStmt.Create(sdLeft, LStripAction), result); + end; end; function TTemplateParser.ruleVariable: IExpr; @@ -1524,11 +1552,12 @@ function TTemplateParser.lookaheadValue: string; exit(''); end; -procedure TTemplateParser.match(const ASymbol: TTemplateSymbol); +function TTemplateParser.match(const ASymbol: TTemplateSymbol): TStripAction; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; + result := TStripAction.saNone; if ASymbol = FLookahead.Token then begin if LSymbol.StripWS then @@ -1540,6 +1569,10 @@ procedure TTemplateParser.match(const ASymbol: TTemplateSymbol); include(FOptions, poStripWS); end; end; + case ASymbol of + vsStartScript, vsEndScript: + result := LSymbol.StripAction; + end; FLookahead := FLexer.GetToken; exit; end; @@ -2321,6 +2354,52 @@ function TLoopStmt.GetOnFirstContainer: ITemplate; exit(FOnFirst); end; +{ TCompositeStmt } + +procedure TCompositeStmt.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +constructor TCompositeStmt.Create(const AFirstStmt, ASecondStmt: IStmt); +begin + FFirstStmt := AFirstStmt; + FSecondStmt := ASecondStmt; +end; + +function TCompositeStmt.GetFirstStmt: IStmt; +begin + exit(FFirstStmt); +end; + +function TCompositeStmt.GetSecondStmt: IStmt; +begin + exit(FSecondStmt); +end; + +{ TStripStmt } + +procedure TStripStmt.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +constructor TStripStmt.Create(const ADirection: TStripDirection; const AAction: TStripAction); +begin + FDirection := ADirection; + FAction := AAction; +end; + +function TStripStmt.GetAction: TStripAction; +begin + exit(FAction); +end; + +function TStripStmt.GetDirection: TStripDirection; +begin + exit(FDirection); +end; + initialization initOps; From 460d8e91f22809e182809cb7116c7540a0f2b980 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:18:21 +0100 Subject: [PATCH 025/138] Remove unnecessary cast in TBlockReplacerVisitor --- src/Sempare.Template.BlockReplacer.pas | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Sempare.Template.BlockReplacer.pas b/src/Sempare.Template.BlockReplacer.pas index c718255..286c661 100644 --- a/src/Sempare.Template.BlockReplacer.pas +++ b/src/Sempare.Template.BlockReplacer.pas @@ -93,16 +93,12 @@ constructor TBlockReplacerVisitor.Create(const AEvalVisitor: IEvaluationTemplate end; procedure TBlockReplacerVisitor.Replace(const ATemplate: ITemplate; const ABlockName: string; const ABlock: ITemplate); -var - LVisitor: ITemplateVisitor; begin - if not supports(self, ITemplateVisitor, LVisitor) then - exit; if not assigned(ABlock) then exit; FBlockName := ABlockName; FReplacementBlock := ABlock; - AcceptVisitor(ATemplate, LVisitor); + AcceptVisitor(ATemplate, self); FReplacementBlock := nil; end; From fbe9baf77eedd5d8efdd9f1deb3f823007af81d5 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:19:27 +0100 Subject: [PATCH 026/138] Remove unnecessary AcceptVisitor() and Position() helper utils --- src/Sempare.Template.Common.pas | 85 ++------------------------------- 1 file changed, 5 insertions(+), 80 deletions(-) diff --git a/src/Sempare.Template.Common.pas b/src/Sempare.Template.Common.pas index 658f449..6bd7b55 100644 --- a/src/Sempare.Template.Common.pas +++ b/src/Sempare.Template.Common.pas @@ -96,18 +96,11 @@ TTemplateVariables = class(TInterfacedObject, ITemplateVariables) property Variables[const AKey: string]: TTemplateValue read GetItem write SetItem; default; end; -function AsVisitorHost(const ATemplate: ITemplate): ITemplateVisitorHost; inline; overload; -function AsVisitorHost(const AExpr: IExpr): ITemplateVisitorHost; inline; overload; -function AsVisitorHost(const AStmt: IStmt): ITemplateVisitorHost; inline; overload; - procedure AcceptVisitor(const ATemplate: ITemplate; const AVisitor: ITemplateVisitor); inline; overload; procedure AcceptVisitor(const AExpr: IExpr; const AVisitor: ITemplateVisitor); inline; overload; procedure AcceptVisitor(const AStmt: IStmt; const AVisitor: ITemplateVisitor); inline; overload; -procedure AcceptVisitor(const AExpr: IExprList; const AVisitor: ITemplateVisitor); overload; -procedure AcceptVisitor(const AHost: ITemplateVisitorHost; const AVisitor: ITemplateVisitor); overload; +procedure AcceptVisitor(const AExpr: IExprList; const AVisitor: ITemplateVisitor); inline; overload; -function Position(const AStmt: IStmt): IPosition; inline; overload; -function Position(const AExpr: IExpr): IPosition; inline; overload; function Position(const APositional: IPosition): string; inline; overload; procedure RaiseError(const APositional: IPosition; const AFormat: string; const AArgs: array of const); overload; @@ -115,109 +108,41 @@ procedure RaiseError(const APositional: IPosition; const AFormat: string); overl procedure RaiseErrorRes(const APositional: IPosition; const ResStringRec: PResStringRec; const AArgs: array of const); overload; procedure RaiseErrorRes(const APositional: IPosition; const ResStringRec: PResStringRec); overload; -function CloneTemplate(const ATemplate: ITemplate): ITemplate; -function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; -function CloneStmt(const AStmt: IStmt): IStmt; - implementation -function CloneTemplate(const ATemplate: ITemplate): ITemplate; -begin - supports(ATemplate.Clone, ITemplate, result); -end; - -function CloneVisitorHost(const AVisitorHost: ITemplateVisitorHost): ITemplateVisitorHost; -begin - supports(AVisitorHost.Clone, ITemplateVisitorHost, result); -end; - -function CloneStmt(const AStmt: IStmt): IStmt; -begin - supports(AStmt.Clone, IStmt, result); -end; - -function AsVisitorHost(const ATemplate: ITemplate): ITemplateVisitorHost; overload; -begin - ATemplate.QueryInterface(ITemplateVisitorHost, result); -end; - -function AsVisitorHost(const AExpr: IExpr): ITemplateVisitorHost; -begin - AExpr.QueryInterface(ITemplateVisitorHost, result); -end; - -function AsVisitorHost(const AStmt: IStmt): ITemplateVisitorHost; -begin - AStmt.QueryInterface(ITemplateVisitorHost, result); -end; - procedure AcceptVisitor(const ATemplate: ITemplate; const AVisitor: ITemplateVisitor); overload; -var - LHost: ITemplateVisitorHost; begin if not assigned(ATemplate) then exit; - LHost := AsVisitorHost(ATemplate); - LHost.Accept(AVisitor); + ATemplate.Accept(AVisitor); end; procedure AcceptVisitor(const AExpr: IExpr; const AVisitor: ITemplateVisitor); -var - LHost: ITemplateVisitorHost; begin if not assigned(AExpr) then exit; - LHost := AsVisitorHost(AExpr); - LHost.Accept(AVisitor); + AExpr.Accept(AVisitor); end; procedure AcceptVisitor(const AStmt: IStmt; const AVisitor: ITemplateVisitor); -var - LHost: ITemplateVisitorHost; begin if not assigned(AStmt) then exit; - LHost := AsVisitorHost(AStmt); - LHost.Accept(AVisitor); -end; - -procedure AcceptVisitor(const AHost: ITemplateVisitorHost; const AVisitor: ITemplateVisitor); -begin - if not assigned(AHost) then - exit; - AHost.Accept(AVisitor); + AStmt.Accept(AVisitor); end; procedure AcceptVisitor(const AExpr: IExprList; const AVisitor: ITemplateVisitor); var - LHost: ITemplateVisitorHost; i: integer; begin if not assigned(AExpr) then exit; for i := 0 to AExpr.Count - 1 do begin - LHost := AsVisitorHost(AExpr[i]); - LHost.Accept(AVisitor); + AExpr[i].Accept(AVisitor); end; end; -function Position(const AStmt: IStmt): IPosition; overload; -var - LSymbol: IPositional; -begin - AStmt.QueryInterface(IPositional, LSymbol); - exit(LSymbol.Position); -end; - -function Position(const AExpr: IExpr): IPosition; overload; -var - LSymbol: IPositional; -begin - AExpr.QueryInterface(IPositional, LSymbol); - exit(LSymbol.Position); -end; - function Position(const APositional: IPosition): string; overload; var LName: string; From 28685483ebb4abbb361f5bf6858f30b9bfee5fc6 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:21:33 +0100 Subject: [PATCH 027/138] Change interface inheritance to minimise interface casting on ITemplate, IStmt and IExpr Added helper methods for trimming down AST node graph --- src/Sempare.Template.AST.pas | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index e57d5d8..99956d2 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -181,19 +181,21 @@ ETemplate = class(Exception); property Position: IPosition read GetPosition; end; - ITemplateVisitorHost = interface + ITemplateVisitorHost = interface(IPosition) ['{BB5F2BF7-390D-4E20-8FD2-DB7609519143}'] procedure Accept(const AVisitor: ITemplateVisitor); function Clone: IInterface; end; - IExpr = interface + IExpr = interface(ITemplateVisitorHost) ['{8C539211-ED84-4963-B894-C569C2F7B2FE}'] end; - IStmt = interface + IStmt = interface(ITemplateVisitorHost) ['{6D37028E-A0C0-41F1-8A59-EDC0C9ADD9C7}'] function Clone: IInterface; + function CloneAsStmt: IStmt; + function Flatten: TArray; end; IDebugStmt = interface(IStmt) @@ -202,21 +204,22 @@ ETemplate = class(Exception); property Stmt: IStmt read GetStmt; end; - ITemplate = interface + ITemplate = interface(ITemplateVisitorHost) ['{93AAB971-5B4B-4959-93F2-6C7DAE15C91B}'] - function GetItem(const AOffset: integer): ITemplateVisitorHost; + function GetItem(const AOffset: integer): IStmt; function GetCount: integer; - function GetLastItem: ITemplateVisitorHost; + function GetLastItem: IStmt; function Clone: IInterface; + function CloneAsTemplate: ITemplate; procedure Optimise; - property Items[const AOffset: integer]: ITemplateVisitorHost read GetItem; + property Items[const AOffset: integer]: IStmt read GetItem; property Count: integer read GetCount; - property LastItem: ITemplateVisitorHost read GetLastItem; + property LastItem: IStmt read GetLastItem; end; ITemplateAdd = interface(ITemplate) ['{64465D68-0E9D-479F-9EF3-A30E75967809}'] - procedure Add(const AItem: ITemplateVisitorHost); + procedure Add(const AItem: IStmt); end; IExtendsStmt = interface(IStmt) From 23a9cc9e460748f3d28fa0e9e18908a96c27757b Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:22:55 +0100 Subject: [PATCH 028/138] Change GStreamWriterProvider defaults to use TNewLineStreamWriter which supports the enhanced trimming. --- src/Sempare.Template.Context.pas | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index e32aa0c..358391d 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -628,10 +628,7 @@ initialization GDefaultEncoding := TEncoding.UTF8WithoutBOM; GStreamWriterProvider := function(const AStream: TStream; AContext: ITemplateContext): TStreamWriter begin - if (eoTrimLines in AContext.Options) or (eoStripRecurringNewlines in AContext.Options) or (eoAllowIgnoreNL in AContext.Options) or (eoInternalUseNewLine in AContext.Options) then - exit(TNewLineStreamWriter.Create(AStream, AContext.Encoding, AContext.NewLine, AContext.Options)) - else - exit(TStreamWriter.Create(AStream, AContext.Encoding)); + exit(TNewLineStreamWriter.Create(AStream, AContext.Encoding, AContext.NewLine, AContext.Options)); end; finalization From c65b8dffa1964ff5011d514e4498b79ef9c3b7f7 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:37:34 +0100 Subject: [PATCH 029/138] Added threaded and timing tests, especially around extends/block --- tests/Sempare.Template.TestInclude.pas | 239 +++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index 383d9f9..79b5173 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -81,6 +81,12 @@ TTestTemplateInclude = class [Test] procedure TestWebForm; + [Test] + procedure TestTimedWebForm; + + [Test] + procedure TestThreadedWebForm; + [Test] procedure TestNestedBody; @@ -92,7 +98,13 @@ TTestTemplateInclude = class implementation uses +{$IFDEF WIN32} + Windows, +{$ENDIF} + System.Math, + System.Classes, System.SysUtils, + System.Diagnostics, System.Generics.Collections, Sempare.Template.Context, Sempare.Template; @@ -438,6 +450,233 @@ constructor TButton.create(const ACaption, AName: string); name := AName; end; +{$IFDEF WIN32} + +function GetNanoseconds: Int64; +var + frequency, counter: Int64; +begin + QueryPerformanceFrequency(frequency); + QueryPerformanceCounter(counter); + Result := (counter * 1000000000) div frequency; +end; +{$ENDIF} + +procedure TTestTemplateInclude.TestThreadedWebForm; + +type + + TTemplateData = record + company: string; + CopyrightYear: integer; + FormName: string; + FormAction: string; + Fields: TArray; + Buttons: TArray; + end; + +var + LTemplateData: TTemplateData; + LTemplate: ITemplate; + i: integer; + LThread: TThread; + LThreads: TObjectList; + LNumThreads: integer; + LNumEvals: integer; +begin + LTemplateData.company := 'Sempare'; + LTemplateData.CopyrightYear := 2023; + LTemplateData.FormName := 'userinfo'; + LTemplateData.FormAction := '/userinfo'; + LTemplateData.Fields := [TField.create('FirstName', 'firstname'), TField.create('LastName', 'lastname'), TField.create('Email', 'email', 'TEmail')]; + LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; + + LTemplate := Template.parse( // + '<% template "TEdit" %><% Caption %><% end %>'#13#10 + // 1 + + '<% template "TEmail" %><% Caption %><% end %>'#13#10 + // 2 + + '<% template "TButton" %><% end %>'#13#10 + // 3 + + '<% template "TForm" %>'#13#10 + // 4 + '
'#13#10 + // 5 + ' '#13#10 + // 6 + ' <% for field of fields %>'#13#10 + // 7 + ' <% include(field.FieldType, field)%>'#13#10 + // 8 + ' <% end %>'#13#10 + // 9 + ' '#13#10 + // + ' '#13#10 + // 14 + ' '#13#10 + // 14 + '
'#13#10 + // 10 + ' <% for button of buttons %>'#13#10 + // 11 + ' <% include("TButton", button) %>'#13#10 + // 12 + ' <% end %>'#13#10 + // 13 + '
'#13#10 + // 15 + '
'#13#10 + // 16 + '<% end %>'#13#10 + // 17 + + '<% template "header" %>'#13#10 + // 18 + ''#13#10 + // 19 + ' '#13#10 + // 20 + ' Welcome to my <% Company %>'#13#10 + // 21 + ' '#13#10 + // 22 + ' '#13#10 + // 23 + '<% end %>'#13#10 + // 24 + + '<% template "footer" %>'#13#10 + // 25 + '

Copyright (c) <% CopyrightYear %>

'#13#10 + // 26 + ' '#13#10 + // 27 + ''#13#10 + // 28 + '<% end %>'#13#10 + // 29 + + '<% template "template" %>'#13#10 + // 30 + '<% include("header") %>'#13#10 + // 31 + '<% block "body" %>Lorem ipsum dolor sit amet, consectetur adipiscing eli...<% end %>'#13#10 + // 32 + '<% include("footer") %>'#13#10 + // 33 + '<% end %>'#13#10 + // 34 + + '<% extends ("template") %>' + // 35 + '<% block "body" %><% include("TForm") %><% end %> '#13#10 + // 36 + '<% end %>'#13#10 // 37 + ); + LThreads := TObjectList.create; + LNumThreads := min(1, CPUCount); + LNumEvals := 500; + for i := 0 to LNumThreads do + begin + LThread := TThread.CreateAnonymousThread( + procedure + var + LResult: string; + i: integer; + LStopWatch: TStopwatch; + LElapsedMs: double; + begin + + LStopWatch := TStopwatch.create; + LStopWatch.Start; + + for i := 0 to LNumEvals do + begin + LResult := Template.Eval(LTemplate, LTemplateData); + Assert.AreEqual(#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10''#13#10' '#13#10' Welcome to my Sempare'#13#10 + // + ' '#13#10' '#13#10''#13#10''#13#10'
'#13#10 + // + ' '#13#10' '#13#10' '#13#10' '#13#10' '#13#10 + // + ' '#13#10' '#13#10' '#13#10 + // + ' '#13#10' '#13#10' '#13#10 + // + '
FirstName' + // + '
LastName
Email
'#13#10' '#13#10 + // + ' '#13#10' '#13#10'
'#13#10'
'#13#10''#13#10''#13#10'

Copyright (c) 2023

'#13#10' '#13#10''#13#10''#13#10''#13#10, LResult); + end; + LStopWatch.Stop; + LElapsedMs := LStopWatch.ElapsedMilliseconds / LNumEvals; + + Assert.IsTrue(LElapsedMs < 0.1300); + end); + LThread.FreeOnTerminate := false; + LThreads.Add(LThread); + end; + for LThread in LThreads do + begin + LThread.Start; + end; + for LThread in LThreads do + begin + LThread.WaitFor; + end; +end; + +procedure TTestTemplateInclude.TestTimedWebForm; + +type + + TTemplateData = record + company: string; + CopyrightYear: integer; + FormName: string; + FormAction: string; + Fields: TArray; + Buttons: TArray; + end; + +var + LTemplateData: TTemplateData; + LTemplate: ITemplate; + i: integer; + LStopWatch: TStopwatch; + LElapsedMs: double; + LIterations: integer; +begin + LTemplateData.company := 'Sempare'; + LTemplateData.CopyrightYear := 2023; + LTemplateData.FormName := 'userinfo'; + LTemplateData.FormAction := '/userinfo'; + LTemplateData.Fields := [TField.create('FirstName', 'firstname'), TField.create('LastName', 'lastname'), TField.create('Email', 'email', 'TEmail')]; + LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; + + LTemplate := Template.parse( // + '<% template "TEdit" %><% Caption %><% end %>'#13#10 + // 1 + + '<% template "TEmail" %><% Caption %><% end %>'#13#10 + // 2 + + '<% template "TButton" %><% end %>'#13#10 + // 3 + + '<% template "TForm" %>'#13#10 + // 4 + '
'#13#10 + // 5 + ' '#13#10 + // 6 + ' <% for field of fields %>'#13#10 + // 7 + ' <% include(field.FieldType, field)%>'#13#10 + // 8 + ' <% end %>'#13#10 + // 9 + ' '#13#10 + // + ' '#13#10 + // 14 + ' '#13#10 + // 14 + '
'#13#10 + // 10 + ' <% for button of buttons %>'#13#10 + // 11 + ' <% include("TButton", button) %>'#13#10 + // 12 + ' <% end %>'#13#10 + // 13 + '
'#13#10 + // 15 + '
'#13#10 + // 16 + '<% end %>'#13#10 + // 17 + + '<% template "header" %>'#13#10 + // 18 + ''#13#10 + // 19 + ' '#13#10 + // 20 + ' Welcome to my <% Company %>'#13#10 + // 21 + ' '#13#10 + // 22 + ' '#13#10 + // 23 + '<% end %>'#13#10 + // 24 + + '<% template "footer" %>'#13#10 + // 25 + '

Copyright (c) <% CopyrightYear %>

'#13#10 + // 26 + ' '#13#10 + // 27 + ''#13#10 + // 28 + '<% end %>'#13#10 + // 29 + + '<% template "template" %>'#13#10 + // 30 + '<% include("header") %>'#13#10 + // 31 + '<% block "body" %>Lorem ipsum dolor sit amet, consectetur adipiscing eli...<% end %>'#13#10 + // 32 + '<% include("footer") %>'#13#10 + // 33 + '<% end %>'#13#10 + // 34 + + '<% extends ("template") %>' + // 35 + '<% block "body" %><% include("TForm") %><% end %> '#13#10 + // 36 + '<% end %>'#13#10 // 37 + ); + + LIterations := 500; + LStopWatch := TStopwatch.create; + LStopWatch.Start; + for i := 1 to LIterations do + begin + Template.Eval(LTemplate, LTemplateData); + end; + LStopWatch.Stop; + LElapsedMs := LStopWatch.ElapsedMilliseconds / LIterations; + + Assert.IsTrue(LElapsedMs < 0.1300); + +end; + procedure TTestTemplateInclude.TestWebForm; type From 1c140cf304f1ba3b36dafc3b52def9ea4c3901e3 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:39:05 +0100 Subject: [PATCH 030/138] Updated tests --- tests/Sempare.Template.Test.pas | 29 ++++++++++++++++++++++++ tests/Sempare.Template.TestContext.pas | 5 +++- tests/Sempare.Template.TestExpr.pas | 1 - tests/Sempare.Template.TestFunctions.pas | 10 ++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 2e48fef..1405585 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -44,8 +44,14 @@ interface [TestFixture] TTestTemplate = class public + [Test] + procedure TestEmpty; + [Test] + procedure TestNonStmt; [Test] procedure TestComment; + [Test, Ignore] + procedure TestHashComment; [Test {$IFNDEF SEMPARE_TEMPLATE_HAS_HTML_ENCODER}, Ignore{$ENDIF}] procedure TestHtmlEncoding; [Test] @@ -153,6 +159,15 @@ procedure TTestTemplate.TestComment; )); end; +procedure TTestTemplate.TestHashComment; +begin + Assert.AreEqual('before after ', Template.Eval( // + 'before ' + // + '<%# this is '#13#10#13#10'a comment %>' + // + 'after ' // + )); +end; + procedure TTestTemplate.TestHtml; type TRec = record @@ -306,6 +321,15 @@ TRec = record Assert.AreEqual('a value', r.Val); end; +procedure TTestTemplate.TestNonStmt; +begin + Assert.WillRaise( + procedure + begin + Template.Eval('<% %>'); + end); +end; + procedure TTestTemplate.TestNoSpace; var ctx: ITemplateContext; @@ -486,6 +510,11 @@ class procedure TMyExceptProc.RaiseExcept(const AValue: string); raise Exception.Create(AValue); end; +procedure TTestTemplate.TestEmpty; +begin + Template.Eval(''); +end; + procedure TTestTemplate.TestException; var LContext: ITemplateContext; diff --git a/tests/Sempare.Template.TestContext.pas b/tests/Sempare.Template.TestContext.pas index be5c434..138342e 100644 --- a/tests/Sempare.Template.TestContext.pas +++ b/tests/Sempare.Template.TestContext.pas @@ -67,8 +67,11 @@ procedure TContextTest.TestVariables; end; procedure TContextTest.TestCRNLTAB; +var + ctx: ITemplateContext; begin - Assert.AreEqual(#10, Template.Eval('<% nl %>')); + ctx := Template.Context(); + Assert.AreEqual(ctx.NewLine, Template.Eval('<% nl %>')); Assert.AreEqual(#13, Template.Eval('<% cr %>')); Assert.AreEqual(#13#10, Template.Eval('<% crnl %>')); Assert.AreEqual(#9, Template.Eval('<% tab %>')); diff --git a/tests/Sempare.Template.TestExpr.pas b/tests/Sempare.Template.TestExpr.pas index 17a9b9c..97714ba 100644 --- a/tests/Sempare.Template.TestExpr.pas +++ b/tests/Sempare.Template.TestExpr.pas @@ -117,7 +117,6 @@ procedure TTestTemplateExpr.TestNotEqual; Assert.AreEqual('result', Template.Eval('<% a:= "123" %><% if a <> ''value'' %>result<% end %>')); Assert.AreEqual('result', Template.Eval('<% a:= "value" %><% if a = ''value'' %>result<% end %>')); - // NOTE: NE is implemented as NOT Equal, so we test both scenarios here Assert.AreEqual('result', Template.Eval('<% if _ <> ''value'' %>result<% end %>', 'x')); Assert.AreEqual('', Template.Eval('<% if _ <> ''value'' %>result<% end %>', 'value')); diff --git a/tests/Sempare.Template.TestFunctions.pas b/tests/Sempare.Template.TestFunctions.pas index 3b52c61..67b1d35 100644 --- a/tests/Sempare.Template.TestFunctions.pas +++ b/tests/Sempare.Template.TestFunctions.pas @@ -278,8 +278,11 @@ procedure TFunctionTest.TestBase64Encode; end; procedure TFunctionTest.TestChrOrd; +var + LCtx: ITemplateContext; begin - Assert.AreEqual(#10, Template.Eval('<% chr(10) %>')); + LCtx := Template.Context(); + Assert.AreEqual(LCtx.NewLine, Template.Eval('<% chr(10) %>')); Assert.AreEqual(#9, Template.Eval('<% chr(9) %>')); Assert.AreEqual('200', Template.Eval('<% ord(chr(200)) %>')); end; @@ -391,14 +394,17 @@ procedure TFunctionTest.TestNum; end; procedure TFunctionTest.TestPadding; +var + LCtx: ITemplateContext; begin + LCtx := Template.Context(); Assert.AreEqual(' 123', Template.Eval('<% padleft(123, 6) %>')); Assert.AreEqual('000123', Template.Eval('<% padleft(123, 6, "0") %>')); Assert.AreEqual('123 ', Template.Eval('<% padright(123, 6) %>')); Assert.AreEqual('123000', Template.Eval('<% padright(123, 6, "0") %>')); Assert.AreEqual(#9#9#9#9#9, Template.Eval('<% tabs(5) %>')); Assert.AreEqual(' ', Template.Eval('<% spaces(5) %>')); - Assert.AreEqual(#10#10#10#10#10, Template.Eval('<% nl(5) %>')); + Assert.AreEqual(LCtx.NewLine + LCtx.NewLine + LCtx.NewLine + LCtx.NewLine + LCtx.NewLine, Template.Eval('<% nl(5) %>')); Assert.AreEqual(#13#10#13#10, Template.Eval('<% crnl(2) %>')); end; From f96f7b54736b81fb9fc27b363df14bce9a743786 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:39:47 +0100 Subject: [PATCH 031/138] Remove TCleanupVisitor --- src/Sempare.Template.CleanupVisitor.pas | 107 ------------------------ src/Sempare.Template.pas | 10 +-- 2 files changed, 1 insertion(+), 116 deletions(-) delete mode 100644 src/Sempare.Template.CleanupVisitor.pas diff --git a/src/Sempare.Template.CleanupVisitor.pas b/src/Sempare.Template.CleanupVisitor.pas deleted file mode 100644 index 9c72728..0000000 --- a/src/Sempare.Template.CleanupVisitor.pas +++ /dev/null @@ -1,107 +0,0 @@ -(*%************************************************************************************************* - * ___ * - * / __| ___ _ __ _ __ __ _ _ _ ___ * - * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * - * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * - * |_| * - **************************************************************************************************** - * * - * Sempare Template Engine * - * * - * * - * https://github.com/sempare/sempare-delphi-template-engine * - **************************************************************************************************** - * * - * Copyright (c) 2019-2023 Sempare Limited * - * * - * Contact: info@sempare.ltd * - * * - * Licensed under the GPL Version 3.0 or the Sempare Commercial License * - * You may not use this file except in compliance with one of these Licenses. * - * You may obtain a copy of the Licenses at * - * * - * https://www.gnu.org/licenses/gpl-3.0.en.html * - * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the Licenses is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * - *************************************************************************************************%*) -unit Sempare.Template.CleanupVisitor; - -interface - -uses - Sempare.Template.AST, - Sempare.Template.Common, - Sempare.Template.Visitor; - -type - ICleanupVisitor = interface(ITemplateVisitor) - ['{E4E25DDD-CA4F-48B9-B3A2-C265EDF611B2}'] - procedure Cleanup(const ATemplate: ITemplate); - end; - - TCleanupVisitor = class(TNoExprTemplateVisitor, ICleanupVisitor) - private - FEvalVisitor: IEvaluationTemplateVisitor; - public - constructor Create(const AEvalVisitor: IEvaluationTemplateVisitor); - - procedure Cleanup(const ATemplate: ITemplate); - - procedure Visit(const AStmt: ITemplate); overload; override; - procedure Visit(const AContainer: ITemplateVisitorHost); overload; override; - procedure Visit(const AStmt: IIncludeStmt); overload; override; - - procedure Visit(const AStmt: IBlockStmt); overload; override; - procedure Visit(const AStmt: IExtendsStmt); overload; override; - - end; - -implementation - -uses - System.SysUtils; - -{ TCleanupVisitor } - -procedure TCleanupVisitor.Visit(const AStmt: IBlockStmt); -begin - inherited Visit(AStmt); -end; - -procedure TCleanupVisitor.Visit(const AStmt: IIncludeStmt); -begin - AcceptVisitor(FEvalVisitor.ResolveTemplate(AStmt.Expr), self); -end; - -procedure TCleanupVisitor.Cleanup(const ATemplate: ITemplate); -begin - AcceptVisitor(ATemplate, self); -end; - -constructor TCleanupVisitor.Create(const AEvalVisitor: IEvaluationTemplateVisitor); -begin - FEvalVisitor := AEvalVisitor; -end; - -procedure TCleanupVisitor.Visit(const AContainer: ITemplateVisitorHost); -begin - inherited Visit(AContainer); -end; - -procedure TCleanupVisitor.Visit(const AStmt: ITemplate); -begin - inherited Visit(AStmt); -end; - -procedure TCleanupVisitor.Visit(const AStmt: IExtendsStmt); -begin - AcceptVisitor(FEvalVisitor.ResolveTemplate(AStmt.Name), self); -end; - -end. diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index fb1cac3..187619f 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -141,7 +141,6 @@ implementation VCL.Dialogs, {$ENDIF} {$ENDIF} - Sempare.Template.CleanupVisitor, Sempare.Template.Evaluate, Sempare.Template.VariableExtraction, Sempare.Template.PrettyPrint; @@ -172,7 +171,6 @@ class procedure Template.Eval(const AContext: ITemplateContext; const ATempla LValue: TTemplateValue; LTemplateVisitor: IEvaluationTemplateVisitor; LTemplate: ITemplate; - LCleanup: ICleanupVisitor; begin {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} {$IFDEF MSWINDOWS} @@ -190,13 +188,7 @@ class procedure Template.Eval(const AContext: ITemplateContext; const ATempla {$ENDIF} LValue := TTemplateValue.From(AValue); LTemplateVisitor := TEvaluationTemplateVisitor.Create(AContext, LValue, AStream); - - // need a lock around cleanup actually - // LCleanup := TCleanupVisitor.Create(LTemplateVisitor); - // LCleanup.Cleanup(ATemplate); - - LTemplate := CloneTemplate(ATemplate); - + LTemplate := ATemplate.CloneAsTemplate; AcceptVisitor(LTemplate, LTemplateVisitor); end; From 79af747162b6d58bccf3fcf66c1fedfb8ad18f3d Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:40:31 +0100 Subject: [PATCH 032/138] Improved formatting for IForRangeStmt --- src/Sempare.Template.PrettyPrint.pas | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 11f2662..535d58d 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -331,11 +331,11 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IForInStmt); procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IForRangeStmt); begin tab(); - write('<%% for %s := (', [AStmt.Variable]); + write('<%% for %s := ', [AStmt.Variable]); AcceptVisitor(AStmt.LowExpr, self); - write(') %s (', [ForopToStr(AStmt.ForOp)]); + write(' %s ', [ForopToStr(AStmt.ForOp)]); AcceptVisitor(AStmt.HighExpr, self); - write(') '); + write(' '); if AStmt.StepExpr <> nil then begin write(' step '); From b466951544044908d88a11bcf71c90e38f0bda98 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:41:19 +0100 Subject: [PATCH 033/138] Removed need to visit on ITemplateVisitorHost --- src/Sempare.Template.Visitor.pas | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Sempare.Template.Visitor.pas b/src/Sempare.Template.Visitor.pas index 84247c2..7cc232a 100644 --- a/src/Sempare.Template.Visitor.pas +++ b/src/Sempare.Template.Visitor.pas @@ -44,7 +44,6 @@ ETemplateVisitor = class(ETemplate); TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) public procedure Visit(const AContainer: ITemplate); overload; virtual; - procedure Visit(const AContainer: ITemplateVisitorHost); overload; virtual; procedure Visit(const AExpr: IExpr); overload; virtual; procedure Visit(const AExpr: IBinopExpr); overload; virtual; @@ -127,11 +126,6 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IStmt); raise ETemplateVisitor.Create(SStatementNotSupportedInVisitor); end; -procedure TBaseTemplateVisitor.Visit(const AContainer: ITemplateVisitorHost); -begin - AcceptVisitor(AContainer, self); -end; - procedure TBaseTemplateVisitor.Visit(const AExpr: IExpr); begin raise ETemplateVisitor.Create(SExpressionNotSupportedInVisitor); From 66da2319754b8e32c13cea6fc481e50504a175be Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:45:22 +0100 Subject: [PATCH 034/138] Updated TNewLineStreamWriter to support IStripStmt Refactor casting based on interface inheritance --- src/Sempare.Template.Evaluate.pas | 139 ++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 35 deletions(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 4e68336..a267bcf 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -67,6 +67,7 @@ TNewLineStreamWriter = class(TStreamWriter) FStripRecurringNL: boolean; FRemoveEmpty: boolean; FLastNL: boolean; + FStripStmt: IStripStmt; procedure TrimEndOfLine; procedure TrimLast; procedure DoWrite(); @@ -74,6 +75,7 @@ TNewLineStreamWriter = class(TStreamWriter) constructor Create(const AStream: TStream; const AEncoding: TEncoding; const ANL: string; const AOptions: TTemplateEvaluationOptions); destructor Destroy; override; procedure Write(const AString: string); override; + procedure Handle(const AStmt: IStripStmt); property IgnoreNewLine: boolean read FIgnoreNewline write FIgnoreNewline; end; @@ -224,7 +226,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IVariableDerefExpr); LAllowRootDeref.SetValue(AExpr.DerefType = dtArray); LDerefKey := EvalExpr(AExpr.DerefExpr); - LDerefedValue := Deref(Position(AExpr), LDerefObj, LDerefKey, eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext); + LDerefedValue := Deref(AExpr, LDerefObj, LDerefKey, eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext); FEvalStack.push(LDerefedValue); end; @@ -239,15 +241,15 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IBinopExpr); LRight := EvalExpr(AExpr.RightExpr); case AExpr.BinOp of boIN: - LResult := contains(Position(AExpr.RightExpr), LLeft, LRight, FContext); + LResult := contains(AExpr.RightExpr, LLeft, LRight, FContext); boAND: begin - AssertBoolean(Position(AExpr.LeftExpr), LLeft, LRight); + AssertBoolean(AExpr.LeftExpr, LLeft, LRight); LResult := AsBoolean(LLeft) and AsBoolean(LRight); end; boOR: begin - AssertBoolean(Position(AExpr.LeftExpr), LLeft, LRight); + AssertBoolean(AExpr.LeftExpr, LLeft, LRight); LResult := AsBoolean(LLeft) or AsBoolean(LRight); end; boPlus: @@ -258,30 +260,30 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IBinopExpr); else if isStrLike(LLeft) and isNumLike(LRight) then LResult := LLeft.AsString + AsString(LRight, FContext) else - RaiseError(Position(AExpr.LeftExpr), SStringOrNumericTypesExpected); + RaiseError(AExpr.LeftExpr, SStringOrNumericTypesExpected); boMinus: begin - AssertNumeric(Position(AExpr.LeftExpr), LLeft, LRight); + AssertNumeric(AExpr.LeftExpr, LLeft, LRight); LResult := AsNum(LLeft, FContext) - AsNum(LRight, FContext); end; boSlash: begin - AssertNumeric(Position(AExpr.LeftExpr), LLeft, LRight); + AssertNumeric(AExpr.LeftExpr, LLeft, LRight); LResult := AsNum(LLeft, FContext) / AsNum(LRight, FContext); end; boDiv: begin - AssertNumeric(Position(AExpr.LeftExpr), LLeft, LRight); + AssertNumeric(AExpr.LeftExpr, LLeft, LRight); LResult := trunc(AsNum(LLeft, FContext)) div trunc(AsNum(LRight, FContext)); end; boMult: begin - AssertNumeric(Position(AExpr.LeftExpr), LLeft, LRight); + AssertNumeric(AExpr.LeftExpr, LLeft, LRight); LResult := AsNum(LLeft, FContext) * AsNum(LRight, FContext); end; boMod: begin - AssertNumeric(Position(AExpr.LeftExpr), LLeft, LRight); + AssertNumeric(AExpr.LeftExpr, LLeft, LRight); LResult := AsInt(LLeft, FContext) mod AsInt(LRight, FContext); end; boEQ: @@ -297,7 +299,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IBinopExpr); boGTE: LResult := not isLessThan(LLeft, LRight, FContext); else - RaiseError(Position(AExpr.LeftExpr), SBinOpNotSupported); + RaiseError(AExpr.LeftExpr, SBinOpNotSupported); end; FEvalStack.push(LResult); end; @@ -317,7 +319,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IUnaryExpr); FEvalStack.push(not AsBoolean(LValue)); end else - RaiseError(Position(AExpr), SUnaryOpNotSupported); + RaiseError(AExpr, SUnaryOpNotSupported); end; end; @@ -333,17 +335,17 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IVariableExpr); if LValue.IsEmpty then begin try - LValue := Deref(Position(AExpr), LStackFrame.Root, AExpr.variable, eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext); + LValue := Deref(AExpr, LStackFrame.Root, AExpr.variable, eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext); except on e: exception do begin if (eoRaiseErrorWhenVariableNotFound in FContext.Options) then - RaiseError(Position(AExpr), SCannotFindValiable, [AExpr.variable]); + RaiseError(AExpr, SCannotFindValiable, [AExpr.variable]); end; end; end; if LValue.IsEmpty and (eoRaiseErrorWhenVariableNotFound in FContext.Options) then - RaiseError(Position(AExpr), SCannotFindValiable, [AExpr.variable]); + RaiseError(AExpr, SCannotFindValiable, [AExpr.variable]); end else begin @@ -393,7 +395,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IWhileStmt); begin if not EvalExprAsBoolean(AStmt.Condition) or (coBreak in FLoopOptions) then break; - CheckRunTime(Position(AStmt)); + CheckRunTime(AStmt); if (LOffset = -1) or (i >= LOffset) then begin if LFirst then @@ -461,7 +463,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); exit(true); FStackFrames.peek[LOOP_IDX_NAME] := i; exclude(FLoopOptions, coContinue); - CheckRunTime(Position(AStmt)); + CheckRunTime(AStmt); if (LOffset = -1) or (i >= LOffset) then begin if LFirst then @@ -523,10 +525,10 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); end; LEnumGetEnumeratorMethod := LLoopExprType.GetMethod('GetEnumerator'); if LEnumGetEnumeratorMethod = nil then - RaiseError(Position(AStmt), SGetEnumeratorNotFoundOnObject); + RaiseError(AStmt, SGetEnumeratorNotFoundOnObject); LEnumValue := LEnumGetEnumeratorMethod.Invoke(LLoopExpr.AsObject, []); if LEnumValue.IsEmpty then - RaiseErrorRes(Position(AStmt), @SValueIsNotEnumerable); + RaiseErrorRes(AStmt, @SValueIsNotEnumerable); LEnumObj := LEnumValue.AsObject; try LLoopExprType := GRttiContext.GetType(LEnumObj.ClassType); @@ -553,7 +555,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); begin LArrayType := LLoopExprType as TRttiArrayType; if LArrayType.DimensionCount <> 1 then - RaiseError(Position(AStmt), SOnlyOneDimensionalArraysAreSupported); + RaiseError(AStmt, SOnlyOneDimensionalArraysAreSupported); LDimOrdType := LArrayType.Dimensions[0] as TRttiOrdinalType; if LDimOrdType = nil then LMin := 0 @@ -630,7 +632,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForInStmt); tkDynArray: VisitDynArray; else - RaiseError(Position(AStmt), SGetEnumeratorNotFoundOnObject); + RaiseError(AStmt, SGetEnumeratorNotFoundOnObject); end; end; FStackFrames.pop; @@ -804,7 +806,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForRangeStmt); end else begin - RaiseErrorRes(Position(AStmt), @STypeNotSupported); + RaiseErrorRes(AStmt, @STypeNotSupported); end; end; if LStep <> -1 then @@ -832,7 +834,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IForRangeStmt); AcceptVisitor(AStmt.BetweenItemsContainer, self); end; exclude(FLoopOptions, coContinue); - CheckRunTime(Position(AStmt)); + CheckRunTime(AStmt); AcceptVisitor(AStmt.Container, self); inc(LIdx, LDelta); inc(i); @@ -888,7 +890,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIncludeStmt); if HasBreakOrContinue then exit; LTemplateName := EvalExprAsString(AStmt.Expr); - LTemplate := ResolveTemplate(Position(AStmt.Expr), LTemplateName); + LTemplate := ResolveTemplate(AStmt.Expr, LTemplateName); if assigned(LTemplate) then begin FStackFrames.push(FStackFrames.peek.Clone()); @@ -986,7 +988,7 @@ function TEvaluationTemplateVisitor.Invoke(const AFuncCall: IFunctionCallExpr; c if length(LMethod.GetParameters) = 0 then result := LMethod.Invoke(nil, []) else - result := LMethod.Invoke(nil, GetArgs(Position(AFuncCall), LMethod, AArgs, FContext)); + result := LMethod.Invoke(nil, GetArgs(AFuncCall, LMethod, AArgs, FContext)); if result.IsType then result := result.AsType(); end; @@ -1003,7 +1005,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IFunctionCallExpr); FEvalStack.push(LResult); except on e: exception do - RaiseError(Position(AExpr), e.Message); + RaiseError(AExpr, e.Message); end; end; @@ -1031,7 +1033,7 @@ function TEvaluationTemplateVisitor.ResolveTemplate(const APosition: IPosition; function TEvaluationTemplateVisitor.ResolveTemplate(const AExpr: IExpr): ITemplate; begin - exit(ResolveTemplate(Position(AExpr), EvalExprAsString(AExpr))); + exit(ResolveTemplate(AExpr, EvalExprAsString(AExpr))); end; procedure TEvaluationTemplateVisitor.VisitStmt(const AStmt: IStmt); @@ -1056,7 +1058,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IMethodCallExpr); FEvalStack.push(LResult); except on e: exception do - RaiseError(Position(AExpr), AExpr.Method + ':' + e.Message); + RaiseError(AExpr, AExpr.Method + ':' + e.Message); end; end; @@ -1072,7 +1074,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); begin if FIsNLStreamWriter then begin - LSWriter := FStreamWriter as TNewLineStreamWriter; + LSWriter := TNewLineStreamWriter(FStreamWriter); LPrevNewLineState := LSWriter.IgnoreNewLine; try LSWriter.IgnoreNewLine := not AStmt.AllowNewLine; @@ -1120,7 +1122,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IRequireStmt); if AsString(LExprs[LIndex], FContext).ToLower = LInputType then exit; end; - RaiseError(Position(AStmt), SInputOfRequiredTypeNotFound); + RaiseError(AStmt, SInputOfRequiredTypeNotFound); end; procedure TEvaluationTemplateVisitor.Visit(const AExpr: IArrayExpr); @@ -1145,7 +1147,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICycleStmt); LStackFrame := FStackFrames.peek; LValue := LStackFrame[LOOP_IDX_NAME]; except - RaiseErrorRes(Position(AStmt), @SCycleStatementMustBeInALoop); + RaiseErrorRes(AStmt, @SCycleStatementMustBeInALoop); end; FStreamWriter.Write(EvalExprAsString(AStmt.List[LValue.AsInt64 mod AStmt.List.Count])); end; @@ -1163,8 +1165,14 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDebugStmt); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IStripStmt); +var + LSWriter: TNewLineStreamWriter; begin - + if FIsNLStreamWriter then + begin + LSWriter := TNewLineStreamWriter(FStreamWriter); + LSWriter.Handle(AStmt); + end; end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICompositeStmt); @@ -1213,7 +1221,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); if not assigned(AStmt.Container) then begin LName := AStmt.NameAsString(self); - LTemplate := ResolveTemplate(Position(AStmt), LName); + LTemplate := ResolveTemplate(AStmt, LName); AStmt.Container := LTemplate; LResolveNames := true; @@ -1226,7 +1234,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); // we need to clone the original as we do replacements // not currently planning to support 'inherited', to allow to reference the original body in the original template from within the child body. - LTemplate := CloneTemplate(LTemplate); + LTemplate := LTemplate.CloneAsTemplate; if LResolveNames then begin @@ -1282,6 +1290,36 @@ procedure TNewLineStreamWriter.DoWrite(); inherited write(FBuffer.ToString()); FBuffer.clear; end; +{$WARN WIDECHAR_REDUCED OFF} + +const + STRIP_CHARS: array [TStripAction] of set of char = ( // + [' ', #9], // + [' ', #9, #13, #10], // + [] // + ); +{$WARN WIDECHAR_REDUCED ON} + +procedure TNewLineStreamWriter.Handle(const AStmt: IStripStmt); + +begin + if AStmt.Action = saNone then + begin + FStripStmt := nil; + exit; + end; + if AStmt.Direction = sdLeft then + begin + while (FBuffer.length > 0) and charinset(FBuffer.Chars[FBuffer.length - 1], STRIP_CHARS[AStmt.Action]) do + begin + FBuffer.length := FBuffer.length - 1; + end; + end + else + begin + FStripStmt := AStmt; + end; +end; procedure TNewLineStreamWriter.TrimEndOfLine; begin @@ -1309,8 +1347,39 @@ procedure TNewLineStreamWriter.Write(const AString: string); var LChar: char; LIdx: integer; + LHasLooped: boolean; + LProcessed: integer; begin - for LIdx := 1 to length(AString) do + LIdx := 1; + if assigned(FStripStmt) then + begin + LHasLooped := false; + LProcessed := 0; + while LIdx < length(AString) do + begin + LChar := AString[LIdx]; + if charinset(LChar, STRIP_CHARS[FStripStmt.Action]) then + begin + inc(LIdx); + LHasLooped := true; + if LChar = #10 then + begin + FStripStmt := nil; + break; + end; + continue; + end + else + begin + FStripStmt := nil; + break; + end; + inc(LProcessed); + end; + if LHasLooped and assigned(FStripStmt) and (LProcessed = length(AString)) then + exit; + end; + for LIdx := LIdx to length(AString) do begin LChar := AString[LIdx]; if IsWhitespace(LChar) and FStartOfLine and FTrimLines then From c26065961a7e4e97f920ae2af0054ba01e3eacd6 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:47:45 +0100 Subject: [PATCH 035/138] Update implementation classes for interface inheritance changes --- src/Sempare.Template.Parser.pas | 204 ++++++++++++++++++++++++++++---- 1 file changed, 182 insertions(+), 22 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 1022f53..41874ee 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -72,23 +72,37 @@ implementation TTemplate = class(TInterfacedObject, ITemplate, ITemplateAdd, ITemplateVisitorHost) private - FArray: TArray; - FOptimised: boolean; + FPosition: IPosition; + FArray: TArray; + function Flatten: TArray; procedure Optimise; - function GetItem(const AOffset: integer): ITemplateVisitorHost; + function GetFilename: string; + procedure SetFilename(const AFilename: string); + function GetLine: integer; + procedure SetLine(const Aline: integer); + function GetPos: integer; + procedure SetPos(const Apos: integer); + function GetItem(const AOffset: integer): IStmt; function GetCount: integer; - procedure Add(const AItem: ITemplateVisitorHost); - function GetLastItem: ITemplateVisitorHost; + procedure Add(const AItem: IStmt); + function GetLastItem: IStmt; procedure Accept(const AVisitor: ITemplateVisitor); function Clone: IInterface; + function CloneAsTemplate: ITemplate; public - + constructor Create(); end; TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisitorHost) private FPosition: IPosition; function GetPosition: IPosition; + function GetFilename: string; + procedure SetFilename(const AFilename: string); + function GetLine: integer; + procedure SetLine(const Aline: integer); + function GetPos: integer; + procedure SetPos(const Apos: integer); public constructor Create(const APosition: IPosition); destructor Destroy; override; @@ -97,6 +111,8 @@ TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisito end; TAbstractStmt = class abstract(TAbstractBase, IStmt) + function CloneAsStmt: IStmt; + function Flatten: TArray; virtual; end; TDebugStmt = class(TAbstractStmt, IDebugStmt) @@ -202,6 +218,7 @@ TCompositeStmt = class(TAbstractStmt, ICompositeStmt) function GetFirstStmt: IStmt; function GetSecondStmt: IStmt; procedure Accept(const AVisitor: ITemplateVisitor); override; + function Flatten: TArray; override; public constructor Create(const AFirstStmt, ASecondStmt: IStmt); end; @@ -547,6 +564,30 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Parse(const AStream: TStream; const AManagedStream: boolean): ITemplate; end; +function Flatten(const AStmts: TArray): TArray; +var + LStmt: IStmt; +begin + result := nil; + for LStmt in AStmts do + begin + result := result + LStmt.Flatten; + end; +end; + +procedure Optimise(const ATemplates: TArray); +var + LTemplate: ITemplate; +begin + for LTemplate in ATemplates do + begin + if assigned(LTemplate) then + begin + LTemplate.Optimise; + end; + end; +end; + function CreateTemplateParser(AContext: ITemplateContext): ITemplateParser; begin exit(TTemplateParser.Create(AContext)); @@ -641,6 +682,8 @@ function TTemplateParser.AddEndStripStmt(const ATemplate: ITemplate; const AStri var LAdd: ITemplateAdd; begin + if AStripAction = saNone then + exit(ATemplate); result := ATemplate; supports(result, ITemplateAdd, LAdd); LAdd.Add(TStripStmt.Create(sdRight, AStripAction)); @@ -706,7 +749,7 @@ function TTemplateParser.ruleIfStmt: IStmt; begin while (FLookahead.Token = vsELIF) do begin - LContainerAdd.Add(AsVisitorHost(ruleElIfStmt())); + LContainerAdd.Add(ruleElIfStmt()); end; end else if FLookahead.Token = vsElse then @@ -875,7 +918,7 @@ function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTempl end; if (LStmt <> nil) and not supports(LStmt, IElseStmt) then begin - LParentContainer.Add(AsVisitorHost(LStmt)); + LParentContainer.Add(LStmt); end; end; end; @@ -906,6 +949,7 @@ function TTemplateParser.ruleTemplateStmt: IStmt; result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); result := AddStripStmt(result, LStripAction, sdRight); + LContainer.Optimise; end; function TTemplateParser.ruleSignedFactor: IExpr; @@ -1146,6 +1190,7 @@ function TTemplateParser.ruleBlockStmt: IStmt; PopContainer; result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); result := AddStripStmt(result, LStripAction, sdRight); + LContainer.Optimise; end; function TTemplateParser.ruleBreakStmt: IStmt; @@ -1165,12 +1210,14 @@ function TTemplateParser.ruleBreakStmt: IStmt; function TTemplateParser.ruleCommentStmt: IStmt; var LSymbol: ITemplateSymbol; + LStartStripAction: TStripAction; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsComment); + LStartStripAction := match(vsComment); LStripAction := match(vsEndScript); result := TCommentStmt.Create(LSymbol.Position); + result := AddStripStmt(result, LStartStripAction, sdLeft); result := AddStripStmt(result, LStripAction, sdRight); end; @@ -1486,6 +1533,7 @@ function TTemplateParser.ruleForStmt: IStmt; else result := TForRangeStmt.Create(LSymbol.Position, LId, LForOp, LLowValueExpr, LHighValueExpr, LStep, LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem); result := AddStripStmt(result, LEndStripAction, sdRight); + Optimise([LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem]); end; function TTemplateParser.ruleFunctionExpr(const ASymbol: string): IExpr; @@ -1613,6 +1661,7 @@ function TTemplateParser.ruleWhileStmt: IStmt; else result := TWhileStmt.Create(LSymbol.Position, LCondition, LOffsetExpr, LLimitExpr, LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem); result := AddStripStmt(result, LEndStripAction, sdRight); + Optimise([LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem]); end; function TTemplateParser.ruleWithStmt: IStmt; @@ -1643,7 +1692,7 @@ function TTemplateParser.ruleWithStmt: IStmt; PopContainer; result := TWithStmt.Create(LSymbol.Position, LExpr, LContainer); result := AddStripStmt(result, LEndStripAction, sdRight); - + LContainer.Optimise; end; function TTemplateParser.rulePrintStmt: IStmt; @@ -1750,6 +1799,7 @@ function TTemplateParser.ruleExtendsStmt: IStmt; result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); end; result := AddStripStmt(result, LEndStripAction, sdRight); + LContainer.Optimise; end; function TTemplateParser.CurrentContainer: ITemplate; @@ -1834,9 +1884,13 @@ function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boo PushContainer; FLexer := CreateTemplateLexer(FContext, AStream, '', AManagedStream); FLookahead := FLexer.GetToken; - ruleStmts(CurrentContainer, []); - match(vsEOF); result := CurrentContainer; + if AStream.Size > 0 then + begin + ruleStmts(result, []); + end; + match(vsEOF); + result.Optimise; if eoPrettyPrint in FContext.Options then writeln(Template.PrettyPrint(result)); end; @@ -2145,7 +2199,7 @@ procedure TTemplate.Accept(const AVisitor: ITemplateVisitor); end; end; -procedure TTemplate.Add(const AItem: ITemplateVisitorHost); +procedure TTemplate.Add(const AItem: IStmt); var LOffset: integer; begin @@ -2156,23 +2210,43 @@ procedure TTemplate.Add(const AItem: ITemplateVisitorHost); function TTemplate.Clone: IInterface; var - i: ITemplateVisitorHost; + i: IStmt; LTemplate: TTemplate; begin LTemplate := TTemplate.Create; result := LTemplate; for i in FArray do begin - LTemplate.Add(CloneVisitorHost(i)); + LTemplate.Add(i.CloneAsStmt); end; end; +function TTemplate.CloneAsTemplate: ITemplate; +begin + supports(Clone, ITemplate, result); +end; + +constructor TTemplate.Create; +begin + FPosition := TPosition.Create('', 1, 1); +end; + +function TTemplate.Flatten: TArray; +begin + result := Sempare.Template.Parser.Flatten(FArray); +end; + function TTemplate.GetCount: integer; begin exit(length(FArray)); end; -function TTemplate.GetLastItem: ITemplateVisitorHost; +function TTemplate.GetFilename: string; +begin + exit(FPosition.FileName); +end; + +function TTemplate.GetLastItem: IStmt; begin if GetCount = 0 then exit(nil) @@ -2180,14 +2254,53 @@ function TTemplate.GetLastItem: ITemplateVisitorHost; exit(GetItem(GetCount - 1)); end; +function TTemplate.GetLine: integer; +begin + exit(FPosition.line); +end; + +function TTemplate.GetPos: integer; +begin + exit(FPosition.pos); +end; + procedure TTemplate.Optimise; + + function Strip(const AArray: TArray): TArray; + var + LStmt: IStmt; + begin + result := nil; + for LStmt in AArray do + begin + if supports(LStmt, IEndStmt) or supports(LStmt, ICommentStmt) or supports(LStmt, IElseStmt) then + begin + continue; + end; + result := result + [LStmt]; + end; + end; + begin - if FOptimised then - exit; - FOptimised := true; + FArray := Strip(Flatten); +end; + +procedure TTemplate.SetFilename(const AFilename: string); +begin + FPosition.FileName := AFilename; end; -function TTemplate.GetItem(const AOffset: integer): ITemplateVisitorHost; +procedure TTemplate.SetLine(const Aline: integer); +begin + FPosition.line := Aline; +end; + +procedure TTemplate.SetPos(const Apos: integer); +begin + FPosition.pos := Apos; +end; + +function TTemplate.GetItem(const AOffset: integer): IStmt; begin exit(FArray[AOffset]); end; @@ -2311,11 +2424,41 @@ destructor TAbstractBase.Destroy; inherited; end; +function TAbstractBase.GetFilename: string; +begin + exit(FPosition.FileName); +end; + +function TAbstractBase.GetLine: integer; +begin + exit(FPosition.line); +end; + +function TAbstractBase.GetPos: integer; +begin + exit(FPosition.pos); +end; + function TAbstractBase.GetPosition: IPosition; begin exit(FPosition); end; +procedure TAbstractBase.SetFilename(const AFilename: string); +begin + FPosition.FileName := AFilename; +end; + +procedure TAbstractBase.SetLine(const Aline: integer); +begin + FPosition.line := Aline; +end; + +procedure TAbstractBase.SetPos(const Apos: integer); +begin + FPosition.pos := Apos; +end; + { TMethodCallExpr } procedure TMethodCallExpr.Accept(const AVisitor: ITemplateVisitor); @@ -2619,6 +2762,11 @@ constructor TCompositeStmt.Create(const AFirstStmt, ASecondStmt: IStmt); FSecondStmt := ASecondStmt; end; +function TCompositeStmt.Flatten: TArray; +begin + result := Sempare.Template.Parser.Flatten([FFirstStmt, FSecondStmt]); +end; + function TCompositeStmt.GetFirstStmt: IStmt; begin exit(FFirstStmt); @@ -2661,7 +2809,7 @@ procedure TBlockStmt.Accept(const AVisitor: ITemplateVisitor); function TBlockStmt.Clone: IInterface; begin - exit(TBlockStmt.Create(FPosition, FName, CloneTemplate(FContainer))); + exit(TBlockStmt.Create(FPosition, FName, FContainer)); end; constructor TBlockStmt.Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); @@ -2700,7 +2848,7 @@ procedure TExtendsStmt.Accept(const AVisitor: ITemplateVisitor); function TExtendsStmt.Clone: IInterface; begin - exit(TExtendsStmt.Create(FPosition, FName, CloneTemplate(FBlockContainer))); + exit(TExtendsStmt.Create(FPosition, FName, FBlockContainer)); end; constructor TExtendsStmt.Create(const APosition: IPosition; const AName: IExpr; const ABlockContainer: ITemplate); @@ -2752,6 +2900,18 @@ function TAbstractExpr.Clone: IInterface; exit(self); end; +{ TAbstractStmt } + +function TAbstractStmt.CloneAsStmt: IStmt; +begin + supports(Clone, IStmt, result); +end; + +function TAbstractStmt.Flatten: TArray; +begin + result := [self]; +end; + initialization initOps; From c4b872b19dfc0b65bc11df2c98b006da27648af1 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 16:59:11 +0100 Subject: [PATCH 036/138] Update formatting --- src/Sempare.Template.Parser.pas | 547 ++++++++++++++++---------------- 1 file changed, 276 insertions(+), 271 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 41874ee..f290d45 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -513,49 +513,49 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function CurrentContainer: ITemplate; function lookaheadValue: string; - function matchValue(const ASymbol: TTemplateSymbol): string; - procedure match(ASymbol: ITemplateSymbol); overload; inline; - function match(const ASymbol: TTemplateSymbol): TStripAction; overload; - function matchNumber(const ASymbol: TTemplateSymbol): extended; - procedure matchClosingBracket(const AExpect: TTemplateSymbol); + function MatchValue(const ASymbol: TTemplateSymbol): string; + procedure Match(ASymbol: ITemplateSymbol); overload; inline; + function Match(const ASymbol: TTemplateSymbol): TStripAction; overload; + function MatchNumber(const ASymbol: TTemplateSymbol): extended; + procedure MatchClosingBracket(const AExpect: TTemplateSymbol); function AddStripStmt(const AStmt: IStmt; const AStripAction: TStripAction; const ADirection: TStripDirection): IStmt; function AddEndStripStmt(const ATemplate: ITemplate; const AStripAction: TStripAction): ITemplate; private - function ruleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; - function ruleStmt: IStmt; - function ruleIgnoreNewline: IStmt; - function ruleCommentStmt: IStmt; - function ruleIdStmt: IStmt; - function ruleExprStmt: IStmt; - function ruleIncludeStmt: IStmt; - function rulePrintStmt: IStmt; - function ruleEndStmt: IStmt; - function ruleContinueStmt: IStmt; - function ruleBreakStmt: IStmt; - function ruleIfStmt: IStmt; - function ruleElIfStmt: IStmt; - function ruleExprList(const AEndToken: TTemplateSymbol = vsCloseRoundBracket): IExprList; - function ruleAssignStmt(ASymbol: IExpr): IStmt; - function rulePrintStmtVariable(AExpr: IExpr): IStmt; overload; - function ruleForStmt: IStmt; - function ruleWhileStmt: IStmt; - function ruleWithStmt: IStmt; - function ruleCycleStmt: IStmt; - function ruleTemplateStmt: IStmt; - - function ruleBlockStmt: IStmt; - function ruleExtendsStmt: IStmt; - - function ruleExpression: IExpr; - function ruleSimpleExpression: IExpr; - function ruleTerm: IExpr; - function ruleSignedFactor: IExpr; - function ruleFactor: IExpr; - function ruleVariable: IExpr; - - function ruleFunctionExpr(const ASymbol: string): IExpr; - function ruleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; - function ruleRequireStmt: IStmt; + function RuleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; + function RuleStmt: IStmt; + function RuleIgnoreNewline: IStmt; + function RuleCommentStmt: IStmt; + function RuleIdStmt: IStmt; + function RuleExprStmt: IStmt; + function RuleIncludeStmt: IStmt; + function RulePrintStmt: IStmt; + function RuleEndStmt: IStmt; + function RuleContinueStmt: IStmt; + function RuleBreakStmt: IStmt; + function RuleIfStmt: IStmt; + function RuleElIfStmt: IStmt; + function RuleExprList(const AEndToken: TTemplateSymbol = vsCloseRoundBracket): IExprList; + function RuleAssignStmt(ASymbol: IExpr): IStmt; + function RulePrintStmtVariable(AExpr: IExpr): IStmt; overload; + function RuleForStmt: IStmt; + function RuleWhileStmt: IStmt; + function RuleWithStmt: IStmt; + function RuleCycleStmt: IStmt; + function RuleTemplateStmt: IStmt; + + function RuleBlockStmt: IStmt; + function RuleExtendsStmt: IStmt; + + function RuleExpression: IExpr; + function RuleSimpleExpression: IExpr; + function RuleTerm: IExpr; + function RuleSignedFactor: IExpr; + function RuleFactor: IExpr; + function RuleVariable: IExpr; + + function RuleFunctionExpr(const ASymbol: string): IExpr; + function RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; + function RuleRequireStmt: IStmt; function GetValueSeparatorSymbol: TTemplateSymbol; public @@ -607,8 +607,13 @@ function AsValue(AExpr: IExpr): TValue; end; function AsVarString(AExpr: IExpr): string; +var + LVar: IVariableExpr; begin - exit((AExpr as IVariableExpr).Variable); + if supports(AExpr, IVariableExpr, LVar) then + exit(LVar.Variable) + else + exit(''); end; function IsEnd(AStmt: IStmt): boolean; @@ -670,12 +675,12 @@ function TTemplateParser.GetValueSeparatorSymbol: TTemplateSymbol; exit(vsComma); end; -procedure TTemplateParser.matchClosingBracket(const AExpect: TTemplateSymbol); +procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); begin assert(AExpect in [vsCloseRoundBracket, vsCloseSquareBracket]); if FLookahead.Token in ValueSeparators then - match(OppositeValueSepartor(FLookahead.Token)); - match(AExpect); + Match(OppositeValueSepartor(FLookahead.Token)); + Match(AExpect); end; function TTemplateParser.AddEndStripStmt(const ATemplate: ITemplate; const AStripAction: TStripAction): ITemplate; @@ -715,7 +720,7 @@ constructor TTemplateParser.Create(AContext: ITemplateContext); const IF_ELIF_END: TTemplateSymbolSet = [vsELIF, vsElse, vsEND]; -function TTemplateParser.ruleIfStmt: IStmt; +function TTemplateParser.RuleIfStmt: IStmt; var LConditionalExpr: IExpr; LTrueContainer: ITemplate; @@ -729,16 +734,16 @@ function TTemplateParser.ruleIfStmt: IStmt; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poAllowEnd, poAllowElIf]); LSymbol := FLookahead; - match(vsIF); - LConditionalExpr := ruleExpression; + Match(vsIF); + LConditionalExpr := RuleExpression; - LTrueStripAction := match(vsEndScript); + LTrueStripAction := Match(vsEndScript); // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; AddEndStripStmt(LTrueContainer, LTrueStripAction); - ruleStmts(LTrueContainer, IF_ELIF_END); + RuleStmts(LTrueContainer, IF_ELIF_END); PopContainer; PushContainer; @@ -749,19 +754,19 @@ function TTemplateParser.ruleIfStmt: IStmt; begin while (FLookahead.Token = vsELIF) do begin - LContainerAdd.Add(ruleElIfStmt()); + LContainerAdd.Add(RuleElIfStmt()); end; end else if FLookahead.Token = vsElse then begin - match(vsElse); - LFalseStripAction := match(vsEndScript); + Match(vsElse); + LFalseStripAction := Match(vsEndScript); AddEndStripStmt(LFalseContainer, LFalseStripAction); - ruleStmts(LFalseContainer, [vsEND]); + RuleStmts(LFalseContainer, [vsEND]); end; PopContainer; - match(vsEND); - LEndStripAction := match(vsEndScript); + Match(vsEND); + LEndStripAction := Match(vsEndScript); if (eoEvalEarly in FContext.Options) and IsValue(LConditionalExpr) then begin @@ -774,7 +779,7 @@ function TTemplateParser.ruleIfStmt: IStmt; result := AddStripStmt(result, LEndStripAction, sdRight); end; -function TTemplateParser.ruleIgnoreNewline: IStmt; +function TTemplateParser.RuleIgnoreNewline: IStmt; var LSymbol: ITemplateSymbol; LContainerTemplate: ITemplate; @@ -785,23 +790,23 @@ function TTemplateParser.ruleIgnoreNewline: IStmt; LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - match(vsIgnoreNL); - LStripAction := match(vsEndScript); + Match(vsIgnoreNL); + LStripAction := Match(vsEndScript); PushContainer; LContainerTemplate := CurrentContainer; AddEndStripStmt(LContainerTemplate, LStripAction); - ruleStmts(LContainerTemplate, [vsEND]); + RuleStmts(LContainerTemplate, [vsEND]); - match(vsEND); - LEndStripAction := match(vsEndScript); + Match(vsEND); + LEndStripAction := Match(vsEndScript); PopContainer; result := TProcessTemplateStmt.Create(LSymbol.Position, LContainerTemplate, false); result := AddStripStmt(result, LEndStripAction, sdRight); end; -function TTemplateParser.ruleIncludeStmt: IStmt; +function TTemplateParser.RuleIncludeStmt: IStmt; var LSymbol: ITemplateSymbol; LIncludeExpr: IExpr; @@ -811,20 +816,20 @@ function TTemplateParser.ruleIncludeStmt: IStmt; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsInclude); - match(vsOpenRoundBracket); + Match(vsInclude); + Match(vsOpenRoundBracket); - LIncludeExpr := ruleExpression; + LIncludeExpr := RuleExpression; LValueSeparator := GetValueSeparatorSymbol; if FLookahead.Token = LValueSeparator then begin - match(LValueSeparator); - LScopeExpr := ruleExpression; + Match(LValueSeparator); + LScopeExpr := RuleExpression; end; - matchClosingBracket(vsCloseRoundBracket); - LStripAction := match(vsEndScript); + MatchClosingBracket(vsCloseRoundBracket); + LStripAction := Match(vsEndScript); if LScopeExpr <> nil then begin @@ -839,31 +844,31 @@ function TTemplateParser.ruleIncludeStmt: IStmt; result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleRequireStmt: IStmt; +function TTemplateParser.RuleRequireStmt: IStmt; var LSymbol: ITemplateSymbol; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsRequire); - match(vsOpenRoundBracket); - result := TRequireStmt.Create(LSymbol.Position, self.ruleExprList()); - matchClosingBracket(vsCloseRoundBracket); - LStripAction := match(vsEndScript); + Match(vsRequire); + Match(vsOpenRoundBracket); + result := TRequireStmt.Create(LSymbol.Position, self.RuleExprList()); + MatchClosingBracket(vsCloseRoundBracket); + LStripAction := Match(vsEndScript); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; +function TTemplateParser.RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; - match(vsOpenRoundBracket); - result := TMethodCallExpr.Create(LSymbol.Position, AExpr, AsVarString(AMethodExpr), ruleExprList); - matchClosingBracket(vsCloseRoundBracket); + Match(vsOpenRoundBracket); + result := TMethodCallExpr.Create(LSymbol.Position, AExpr, AsVarString(AMethodExpr), RuleExprList); + MatchClosingBracket(vsCloseRoundBracket); end; -function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; +function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; var LStmt: IStmt; LParentContainer: ITemplateAdd; @@ -875,15 +880,15 @@ function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTempl var LText: string; begin - LText := matchValue(vsText); + LText := MatchValue(vsText); if LText = '' then exit(nil); - exit(rulePrintStmtVariable(TValueExpr.Create(LSymbol.Position, LText))); + exit(RulePrintStmtVariable(TValueExpr.Create(LSymbol.Position, LText))); end; procedure SkipStmt; begin - matchValue(vsText) + MatchValue(vsText) end; begin @@ -911,7 +916,7 @@ function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTempl begin if LSymbol.StripWS then exclude(FOptions, poStripWS); - LStmt := ruleStmt; + LStmt := RuleStmt; if LStmt = nil then LLoop := false; end; @@ -923,7 +928,7 @@ function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTempl end; end; -function TTemplateParser.ruleTemplateStmt: IStmt; +function TTemplateParser.RuleTemplateStmt: IStmt; var LExpr: IExpr; LSymbol: ITemplateSymbol; @@ -935,16 +940,16 @@ function TTemplateParser.ruleTemplateStmt: IStmt; LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - match(vsTemplate); - LExpr := ruleExpression; - LContainerStripAction := match(vsEndScript); + Match(vsTemplate); + LExpr := RuleExpression; + LContainerStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; AddEndStripStmt(LContainer, LContainerStripAction); - ruleStmts(CurrentContainer, [vsEND]); + RuleStmts(CurrentContainer, [vsEND]); - match(vsEND); - LStripAction := match(vsEndScript); + Match(vsEND); + LStripAction := Match(vsEndScript); PopContainer; result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); @@ -952,20 +957,20 @@ function TTemplateParser.ruleTemplateStmt: IStmt; LContainer.Optimise; end; -function TTemplateParser.ruleSignedFactor: IExpr; +function TTemplateParser.RuleSignedFactor: IExpr; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; if LSymbol.Token in [vsMinus, vsPLUS] then - match(LSymbol.Token); + Match(LSymbol.Token); - result := ruleFactor; + result := RuleFactor; if LSymbol.Token = vsMinus then begin - result := ruleFactor; + result := RuleFactor; if (eoEvalEarly in FContext.Options) and IsValue(result) then exit(TValueExpr.Create(LSymbol.Position, -asnum(AsValue(result), FContext))) else @@ -973,19 +978,19 @@ function TTemplateParser.ruleSignedFactor: IExpr; end; end; -function TTemplateParser.ruleSimpleExpression: IExpr; +function TTemplateParser.RuleSimpleExpression: IExpr; var LRightExpr: IExpr; LBinOp: TBinOp; LSymbol: ITemplateSymbol; begin - result := ruleTerm(); + result := RuleTerm(); LSymbol := FLookahead; if LSymbol.Token in [vsPLUS, vsMinus, vsOR] then begin TemplateBinop(LSymbol.Token, LBinOp); - match(LSymbol.Token); - LRightExpr := ruleSimpleExpression; + Match(LSymbol.Token); + LRightExpr := RuleSimpleExpression; if (eoEvalEarly in FContext.Options) and IsValue(result) and IsValue(LRightExpr) then begin case LBinOp of @@ -1008,19 +1013,19 @@ function TTemplateParser.ruleSimpleExpression: IExpr; end; end; -function TTemplateParser.ruleTerm: IExpr; +function TTemplateParser.RuleTerm: IExpr; var LRightExpr: IExpr; LBinOp: TBinOp; LSymbol: ITemplateSymbol; begin - result := ruleSignedFactor; + result := RuleSignedFactor; LSymbol := FLookahead; if LSymbol.Token in [vsMULT, vsDIV, vsSLASH, vsMOD, vsAND] then begin TemplateBinop(LSymbol.Token, LBinOp); - match(LSymbol.Token); - LRightExpr := ruleTerm; + Match(LSymbol.Token); + LRightExpr := RuleTerm; if (eoEvalEarly in FContext.Options) and IsValue(result) and IsValue(LRightExpr) then begin @@ -1043,53 +1048,53 @@ function TTemplateParser.ruleTerm: IExpr; end; -function TTemplateParser.ruleStmt: IStmt; +function TTemplateParser.RuleStmt: IStmt; var LSymbol: ITemplateSymbol; LStripAction: TStripAction; begin result := nil; LSymbol := FLookahead; - LStripAction := match(vsStartScript); + LStripAction := Match(vsStartScript); case FLookahead.Token of vsBreak: - result := ruleBreakStmt; + result := RuleBreakStmt; vsContinue: - result := ruleContinueStmt; + result := RuleContinueStmt; vsIgnoreNL: - result := ruleIgnoreNewline; + result := RuleIgnoreNewline; vsComment: - result := ruleCommentStmt; + result := RuleCommentStmt; vsInclude: - result := ruleIncludeStmt; + result := RuleIncludeStmt; vsEND: - result := ruleEndStmt; + result := RuleEndStmt; vsEndScript, vsElse, vsELIF, vsOnBegin, vsOnEnd, vsOnEmpty, vsBetweenItem: ; vsIF: - result := ruleIfStmt; + result := RuleIfStmt; vsFor: - result := ruleForStmt; + result := RuleForStmt; vsCycle: - result := ruleCycleStmt; + result := RuleCycleStmt; vsPrint: - result := rulePrintStmt; + result := RulePrintStmt; vsWhile: - result := ruleWhileStmt; + result := RuleWhileStmt; vswith: - result := ruleWithStmt; + result := RuleWithStmt; vsRequire: - result := ruleRequireStmt; + result := RuleRequireStmt; vsTemplate: - result := ruleTemplateStmt; + result := RuleTemplateStmt; vsID: - result := ruleIdStmt; + result := RuleIdStmt; vsBlock: - result := ruleBlockStmt; + result := RuleBlockStmt; vsExtends: - result := ruleExtendsStmt; + result := RuleExtendsStmt; else - result := ruleExprStmt; + result := RuleExprStmt; end; if (eoEmbedException in FContext.Options) and (result <> nil) then begin @@ -1098,7 +1103,7 @@ function TTemplateParser.ruleStmt: IStmt; result := AddStripStmt(result, LStripAction, sdLeft); end; -function TTemplateParser.ruleVariable: IExpr; +function TTemplateParser.RuleVariable: IExpr; var LId: string; LIdVal: TValue; @@ -1108,7 +1113,7 @@ function TTemplateParser.ruleVariable: IExpr; begin LDone := false; LSymbol := FLookahead; - LId := matchValue(vsID); + LId := MatchValue(vsID); if (eoEvalVarsEarly in FContext.Options) and FContext.TryGetVariable(LId, LIdVal) then result := TValueExpr.Create(LSymbol.Position, LIdVal) else @@ -1119,24 +1124,24 @@ function TTemplateParser.ruleVariable: IExpr; case LSymbol.Token of vsOpenRoundBracket: begin - result := self.ruleFunctionExpr(LId); + result := self.RuleFunctionExpr(LId); end; vsOpenSquareBracket: begin - match(vsOpenSquareBracket); - LExpr := self.ruleExpression; + Match(vsOpenSquareBracket); + LExpr := self.RuleExpression; if (eoEvalVarsEarly in FContext.Options) and IsValue(result) and IsValue(LExpr) then result := TValueExpr.Create(LSymbol.Position, deref(LSymbol.Position, AsValue(result), AsValue(LExpr), eoRaiseErrorWhenVariableNotFound in FContext.Options, FContext)) else result := TVariableDerefExpr.Create(LSymbol.Position, dtArray, result, LExpr); - match(vsCloseSquareBracket); + Match(vsCloseSquareBracket); end; vsDOT: begin - match(vsDOT); - LExpr := TVariableExpr.Create(LSymbol.Position, matchValue(vsID)); + Match(vsDOT); + LExpr := TVariableExpr.Create(LSymbol.Position, MatchValue(vsID)); if FLookahead.Token = vsOpenRoundBracket then - result := ruleMethodExpr(result, LExpr) + result := RuleMethodExpr(result, LExpr) else begin if (eoEvalVarsEarly in FContext.Options) and IsValue(result) and IsValue(LExpr) then @@ -1153,16 +1158,16 @@ function TTemplateParser.ruleVariable: IExpr; end; end; -function TTemplateParser.ruleAssignStmt(ASymbol: IExpr): IStmt; +function TTemplateParser.RuleAssignStmt(ASymbol: IExpr): IStmt; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; - match(vsCOLONEQ); - exit(TAssignStmt.Create(LSymbol.Position, (ASymbol as IVariableExpr).Variable, ruleExpression)); + Match(vsCOLONEQ); + exit(TAssignStmt.Create(LSymbol.Position, AsVarString(ASymbol), RuleExpression)); end; -function TTemplateParser.ruleBlockStmt: IStmt; +function TTemplateParser.RuleBlockStmt: IStmt; var LName: IExpr; LSymbol: ITemplateSymbol; @@ -1175,17 +1180,17 @@ function TTemplateParser.ruleBlockStmt: IStmt; LSymbol := FLookahead; - match(vsBlock); - LName := ruleExpression; - LContainerStripAction := match(vsEndScript); + Match(vsBlock); + LName := RuleExpression; + LContainerStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; AddEndStripStmt(LContainer, LContainerStripAction); - ruleStmts(LContainer, [vsEND]); + RuleStmts(LContainer, [vsEND]); - match(vsEND); - LStripAction := match(vsEndScript); + Match(vsEND); + LStripAction := Match(vsEndScript); PopContainer; result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); @@ -1193,68 +1198,68 @@ function TTemplateParser.ruleBlockStmt: IStmt; LContainer.Optimise; end; -function TTemplateParser.ruleBreakStmt: IStmt; +function TTemplateParser.RuleBreakStmt: IStmt; var LSymbol: ITemplateSymbol; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsBreak); - LStripAction := match(vsEndScript); + Match(vsBreak); + LStripAction := Match(vsEndScript); if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); result := TBreakStmt.Create(LSymbol.Position); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleCommentStmt: IStmt; +function TTemplateParser.RuleCommentStmt: IStmt; var LSymbol: ITemplateSymbol; LStartStripAction: TStripAction; LStripAction: TStripAction; begin LSymbol := FLookahead; - LStartStripAction := match(vsComment); - LStripAction := match(vsEndScript); + LStartStripAction := Match(vsComment); + LStripAction := Match(vsEndScript); result := TCommentStmt.Create(LSymbol.Position); result := AddStripStmt(result, LStartStripAction, sdLeft); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleContinueStmt: IStmt; +function TTemplateParser.RuleContinueStmt: IStmt; var LSymbol: ITemplateSymbol; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsContinue); - LStripAction := match(vsEndScript); + Match(vsContinue); + LStripAction := Match(vsEndScript); if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); result := TContinueStmt.Create(LSymbol.Position); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleCycleStmt: IStmt; +function TTemplateParser.RuleCycleStmt: IStmt; var LSymbol: ITemplateSymbol; LListExpr: IExprList; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsCycle); - match(vsOpenRoundBracket); + Match(vsCycle); + Match(vsOpenRoundBracket); - LListExpr := ruleExprList(); + LListExpr := RuleExprList(); - matchClosingBracket(vsCloseRoundBracket); - LStripAction := match(vsEndScript); + MatchClosingBracket(vsCloseRoundBracket); + LStripAction := Match(vsEndScript); result := TCycleStmt.Create(LSymbol.Position, LListExpr); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleElIfStmt: IStmt; +function TTemplateParser.RuleElIfStmt: IStmt; var LConditionExpr: IExpr; LTrueContainer: ITemplate; @@ -1271,27 +1276,27 @@ function TTemplateParser.ruleElIfStmt: IStmt; LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poHasElse, poAllowEnd]); - LStripAction := match(vsELIF); + LStripAction := Match(vsELIF); - LConditionExpr := ruleExpression; + LConditionExpr := RuleExpression; - LTrueStripAction := match(vsEndScript); + LTrueStripAction := Match(vsEndScript); // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; AddEndStripStmt(LTrueContainer, LTrueStripAction); - ruleStmts(LTrueContainer, IF_ELIF_END); + RuleStmts(LTrueContainer, IF_ELIF_END); PopContainer; if FLookahead.Token = vsElse then begin - match(vsElse); - LFalseStripAction := match(vsEndScript); + Match(vsElse); + LFalseStripAction := Match(vsEndScript); PushContainer; LFalseContainer := self.CurrentContainer; AddEndStripStmt(LFalseContainer, LFalseStripAction); - ruleStmts(LFalseContainer, [vsEND, vsELIF]); + RuleStmts(LFalseContainer, [vsEND, vsELIF]); PopContainer; end; @@ -1307,18 +1312,18 @@ function TTemplateParser.ruleElIfStmt: IStmt; result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleEndStmt: IStmt; +function TTemplateParser.RuleEndStmt: IStmt; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; - // NOTE: we do not match anything as we want lookahead functions to continue to work + // NOTE: we do not Match anything as we want lookahead functions to continue to work if not(poAllowEnd in FOptions) then RaiseError(LSymbol.Position, SEndNotExpected); exit(TEndStmt.Create(LSymbol.Position)); end; -function TTemplateParser.ruleExpression: IExpr; +function TTemplateParser.RuleExpression: IExpr; var LSymbol: ITemplateSymbol; LRight: IExpr; @@ -1326,13 +1331,13 @@ function TTemplateParser.ruleExpression: IExpr; LFalseExpr: IExpr; LBinOp: TBinOp; begin - result := ruleSimpleExpression(); + result := RuleSimpleExpression(); LSymbol := FLookahead; if LSymbol.Token in [vsEQ, vsNotEQ, vsLT, vsLTE, vsGT, vsGTE, vsIn] then begin TemplateBinop(LSymbol.Token, LBinOp); - match(LSymbol); - LRight := ruleExpression; + Match(LSymbol); + LRight := RuleExpression; if (eoEvalEarly in FContext.Options) and IsValue(result) and IsValue(LRight) then begin case LBinOp of @@ -1355,10 +1360,10 @@ function TTemplateParser.ruleExpression: IExpr; if FLookahead.Token = vsQUESTION then begin - match(vsQUESTION); - LTrueExpr := ruleExpression(); - match(vsColon); - LFalseExpr := ruleExpression(); + Match(vsQUESTION); + LTrueExpr := RuleExpression(); + Match(vsColon); + LFalseExpr := RuleExpression(); if (eoEvalEarly in FContext.Options) and IsValue(result) then begin @@ -1371,7 +1376,7 @@ function TTemplateParser.ruleExpression: IExpr; end; end; -function TTemplateParser.ruleFactor: IExpr; +function TTemplateParser.RuleFactor: IExpr; var LSymbol: ITemplateSymbol; @@ -1380,30 +1385,30 @@ function TTemplateParser.ruleFactor: IExpr; case LSymbol.Token of vsOpenSquareBracket: begin - match(vsOpenSquareBracket); - result := TArrayExpr.Create(LSymbol.Position, ruleExprList(vsCloseSquareBracket)); - matchClosingBracket(vsCloseSquareBracket); + Match(vsOpenSquareBracket); + result := TArrayExpr.Create(LSymbol.Position, RuleExprList(vsCloseSquareBracket)); + MatchClosingBracket(vsCloseSquareBracket); exit; end; vsOpenRoundBracket: begin - match(vsOpenRoundBracket); - result := ruleExpression; - matchClosingBracket(vsCloseRoundBracket); + Match(vsOpenRoundBracket); + result := RuleExpression; + MatchClosingBracket(vsCloseRoundBracket); exit; end; vsString: - exit(TValueExpr.Create(LSymbol.Position, matchValue(vsString))); + exit(TValueExpr.Create(LSymbol.Position, MatchValue(vsString))); vsNumber: - exit(TValueExpr.Create(LSymbol.Position, matchNumber(vsNumber))); + exit(TValueExpr.Create(LSymbol.Position, MatchNumber(vsNumber))); vsBoolean: - exit(TValueExpr.Create(LSymbol.Position, matchValue(vsBoolean) = 'true')); + exit(TValueExpr.Create(LSymbol.Position, MatchValue(vsBoolean) = 'true')); vsID: - exit(self.ruleVariable()); + exit(self.RuleVariable()); vsNOT: begin - match(vsNOT); - result := ruleExpression; + Match(vsNOT); + result := RuleExpression; if (eoEvalEarly in FContext.Options) and IsValue(result) then exit(TValueExpr.Create(LSymbol.Position, not AsBoolean(AsValue(result)))) else @@ -1415,7 +1420,7 @@ function TTemplateParser.ruleFactor: IExpr; const ONFIRST_ONEND_ONLOOP_ELSE: TTemplateSymbolSet = [vsOnBegin, vsOnEnd, vsOnEmpty, vsBetweenItem, vsEND]; -function TTemplateParser.ruleForStmt: IStmt; +function TTemplateParser.RuleForStmt: IStmt; var LId: string; LRangeExpr: IExpr; @@ -1460,58 +1465,58 @@ function TTemplateParser.ruleForStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); - match(vsFor); - LId := matchValue(vsID); + Match(vsFor); + LId := MatchValue(vsID); if FLookahead.Token in [vsIn, vsOf] then begin LForOp := TemplateForop(LSymbol.Position, FLookahead.Token); - match(FLookahead.Token); - LRangeExpr := ruleExpression; + Match(FLookahead.Token); + LRangeExpr := RuleExpression; end else begin - match(vsCOLONEQ); - LLowValueExpr := ruleExpression(); + Match(vsCOLONEQ); + LLowValueExpr := RuleExpression(); LForOp := TemplateForop(LSymbol.Position, FLookahead.Token); if FLookahead.Token in [vsDownto, vsTo] then - match(FLookahead.Token) + Match(FLookahead.Token) else RaiseError(LSymbol.Position, SUnexpectedToken); - LHighValueExpr := ruleExpression(); + LHighValueExpr := RuleExpression(); end; if FLookahead.Token = vsOffset then begin - match(vsOffset); - LOffsetExpr := ruleExpression(); + Match(vsOffset); + LOffsetExpr := RuleExpression(); end; if FLookahead.Token = vsLimit then begin - match(vsLimit); - LLimitExpr := ruleExpression(); + Match(vsLimit); + LLimitExpr := RuleExpression(); end; if FLookahead.Token = vsStep then begin - match(vsStep); - LStep := ruleExpression(); + Match(vsStep); + LStep := RuleExpression(); end; - LContainerStripAction := match(vsEndScript); + LContainerStripAction := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; repeat if (i > 1) and (i mod 2 = 0) then begin - match(LBlockSymbol); - LContainerStripAction := match(vsEndScript); + Match(LBlockSymbol); + LContainerStripAction := Match(vsEndScript); end; PushContainer; LContainerTemplate := CurrentContainer; AddEndStripStmt(LContainerTemplate, LContainerStripAction); - LBlockSymbol := ruleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); + LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); PopContainer; if (i mod 2 = 0) then begin @@ -1520,13 +1525,13 @@ function TTemplateParser.ruleForStmt: IStmt; LPrevSymbol := LBlockSymbol; inc(i); until LBlockSymbol = vsEND; - match(vsEND); + Match(vsEND); if LOnLoop = nil then begin LOnLoop := LContainerTemplate; end; - LEndStripAction := match(vsEndScript); + LEndStripAction := Match(vsEndScript); if LForOp in [TForOp.foIn, TForOp.foOf] then result := TForInStmt.Create(LSymbol.Position, LId, LForOp, LRangeExpr, LOffsetExpr, LLimitExpr, LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem) @@ -1536,7 +1541,7 @@ function TTemplateParser.ruleForStmt: IStmt; Optimise([LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem]); end; -function TTemplateParser.ruleFunctionExpr(const ASymbol: string): IExpr; +function TTemplateParser.RuleFunctionExpr(const ASymbol: string): IExpr; var LFunctions: TArray; LSymbol: ITemplateSymbol; @@ -1544,33 +1549,33 @@ function TTemplateParser.ruleFunctionExpr(const ASymbol: string): IExpr; LSymbol := FLookahead; if not FContext.TryGetFunction(ASymbol, LFunctions) then RaiseError(LSymbol.Position, SFunctionNotRegisteredInContext, [ASymbol]); - match(vsOpenRoundBracket); - result := TFunctionCallExpr.Create(LSymbol.Position, LFunctions, ruleExprList); - matchClosingBracket(vsCloseRoundBracket); + Match(vsOpenRoundBracket); + result := TFunctionCallExpr.Create(LSymbol.Position, LFunctions, RuleExprList); + MatchClosingBracket(vsCloseRoundBracket); end; -function TTemplateParser.ruleIdStmt: IStmt; +function TTemplateParser.RuleIdStmt: IStmt; var LSymbol: ITemplateSymbol; LExpr: IExpr; LStripAction: TStripAction; begin LSymbol := FLookahead; - LExpr := ruleVariable; + LExpr := RuleVariable; if FLookahead.Token = vsCOLONEQ then begin - result := ruleAssignStmt(LExpr); + result := RuleAssignStmt(LExpr); end else begin LExpr := TEncodeExpr.Create(LSymbol.Position, LExpr); - result := rulePrintStmtVariable(LExpr); + result := RulePrintStmtVariable(LExpr); end; - LStripAction := match(vsEndScript); + LStripAction := Match(vsEndScript); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleWhileStmt: IStmt; +function TTemplateParser.RuleWhileStmt: IStmt; var LCondition: IExpr; LOptions: IPreserveValue; @@ -1612,35 +1617,35 @@ function TTemplateParser.ruleWhileStmt: IStmt; LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); - match(vsWhile); - LCondition := ruleExpression; + Match(vsWhile); + LCondition := RuleExpression; if FLookahead.Token = vsOffset then begin - match(vsOffset); - LOffsetExpr := ruleExpression(); + Match(vsOffset); + LOffsetExpr := RuleExpression(); end; if FLookahead.Token = vsLimit then begin - match(vsLimit); - LLimitExpr := ruleExpression(); + Match(vsLimit); + LLimitExpr := RuleExpression(); end; - LContainerStripAction := match(vsEndScript); + LContainerStripAction := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; repeat if (i > 1) and (i mod 2 = 0) then begin - match(LBlockSymbol); - LContainerStripAction := match(vsEndScript); + Match(LBlockSymbol); + LContainerStripAction := Match(vsEndScript); end; PushContainer; LContainerTemplate := CurrentContainer; AddEndStripStmt(LContainerTemplate, LContainerStripAction); - LBlockSymbol := ruleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); + LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); PopContainer; if (i mod 2 = 0) then begin @@ -1649,12 +1654,12 @@ function TTemplateParser.ruleWhileStmt: IStmt; LPrevSymbol := LBlockSymbol; inc(i); until LBlockSymbol = vsEND; - match(vsEND); + Match(vsEND); if LOnLoop = nil then begin LOnLoop := LContainerTemplate; end; - LEndStripAction := match(vsEndScript); + LEndStripAction := Match(vsEndScript); if (eoEvalEarly in FContext.Options) and IsValue(LCondition) and not AsBoolean(AsValue(LCondition)) then result := nil @@ -1664,7 +1669,7 @@ function TTemplateParser.ruleWhileStmt: IStmt; Optimise([LOnLoop, LOnFirst, LOnEnd, LOnEmpty, LBetweenItem]); end; -function TTemplateParser.ruleWithStmt: IStmt; +function TTemplateParser.RuleWithStmt: IStmt; var LExpr: IExpr; LSymbol: ITemplateSymbol; @@ -1677,17 +1682,17 @@ function TTemplateParser.ruleWithStmt: IStmt; LSymbol := FLookahead; - match(vswith); - LExpr := ruleExpression; - LContainerStripAction := match(vsEndScript); + Match(vswith); + LExpr := RuleExpression; + LContainerStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; AddEndStripStmt(LContainer, LContainerStripAction); - ruleStmts(LContainer, [vsEND]); + RuleStmts(LContainer, [vsEND]); - match(vsEND); - LEndStripAction := match(vsEndScript); + Match(vsEND); + LEndStripAction := Match(vsEndScript); PopContainer; result := TWithStmt.Create(LSymbol.Position, LExpr, LContainer); @@ -1695,23 +1700,23 @@ function TTemplateParser.ruleWithStmt: IStmt; LContainer.Optimise; end; -function TTemplateParser.rulePrintStmt: IStmt; +function TTemplateParser.RulePrintStmt: IStmt; var LSymbol: ITemplateSymbol; LExpr: IExpr; LStripAction: TStripAction; begin LSymbol := FLookahead; - match(vsPrint); - match(vsOpenRoundBracket); - LExpr := ruleExpression; - matchClosingBracket(vsCloseRoundBracket); - LStripAction := match(vsEndScript); + Match(vsPrint); + Match(vsOpenRoundBracket); + LExpr := RuleExpression; + MatchClosingBracket(vsCloseRoundBracket); + LStripAction := Match(vsEndScript); result := TPrintStmt.Create(LSymbol.Position, LExpr); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.rulePrintStmtVariable(AExpr: IExpr): IStmt; +function TTemplateParser.RulePrintStmtVariable(AExpr: IExpr): IStmt; var LSymbol: ITemplateSymbol; LValueExpr: IValueExpr; @@ -1722,7 +1727,7 @@ function TTemplateParser.rulePrintStmtVariable(AExpr: IExpr): IStmt; exit(TPrintStmt.Create(LSymbol.Position, AExpr)); end; -function TTemplateParser.ruleExprList(const AEndToken: TTemplateSymbol): IExprList; +function TTemplateParser.RuleExprList(const AEndToken: TTemplateSymbol): IExprList; var LSymbol: ITemplateSymbol; LValueSeparator: TTemplateSymbol; @@ -1730,27 +1735,27 @@ function TTemplateParser.ruleExprList(const AEndToken: TTemplateSymbol): IExprLi LSymbol := FLookahead; result := TExprList.Create(LSymbol.Position); if FLookahead.Token <> AEndToken then - result.AddExpr(ruleExpression); + result.AddExpr(RuleExpression); LValueSeparator := GetValueSeparatorSymbol; while FLookahead.Token = LValueSeparator do begin - match(LValueSeparator); - result.AddExpr(ruleExpression); + Match(LValueSeparator); + result.AddExpr(RuleExpression); end; end; -function TTemplateParser.ruleExprStmt: IStmt; +function TTemplateParser.RuleExprStmt: IStmt; var LSymbol: ITemplateSymbol; LStripAction: TStripAction; begin LSymbol := FLookahead; - result := rulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, ruleExpression)); - LStripAction := match(vsEndScript); + result := RulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, RuleExpression)); + LStripAction := Match(vsEndScript); result := AddStripStmt(result, LStripAction, sdRight); end; -function TTemplateParser.ruleExtendsStmt: IStmt; +function TTemplateParser.RuleExtendsStmt: IStmt; var LName: IExpr; LScopeExpr: IExpr; @@ -1764,27 +1769,27 @@ function TTemplateParser.ruleExtendsStmt: IStmt; LSymbol := FLookahead; - match(vsExtends); - match(vsOpenRoundBracket); - LName := ruleExpression; + Match(vsExtends); + Match(vsOpenRoundBracket); + LName := RuleExpression; if FLookahead.Token = vsComma then begin - match(vsComma); - LScopeExpr := ruleExpression; + Match(vsComma); + LScopeExpr := RuleExpression; end; - match(vsCloseRoundBracket); + Match(vsCloseRoundBracket); - match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block + Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block PushContainer; LContainer := CurrentContainer; - ruleStmts(LContainer, [vsEND]); + RuleStmts(LContainer, [vsEND]); - match(vsEND); - LEndStripAction := match(vsEndScript); + Match(vsEND); + LEndStripAction := Match(vsEndScript); PopContainer; @@ -1826,7 +1831,7 @@ function TTemplateParser.lookaheadValue: string; exit(''); end; -function TTemplateParser.match(const ASymbol: TTemplateSymbol): TStripAction; +function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripAction; var LSymbol: ITemplateSymbol; begin @@ -1853,17 +1858,17 @@ function TTemplateParser.match(const ASymbol: TTemplateSymbol): TStripAction; RaiseError(LSymbol.Position, format(SParsingErrorExpecting, [TemplateSymbolToString(ASymbol)])); end; -procedure TTemplateParser.match(ASymbol: ITemplateSymbol); +procedure TTemplateParser.Match(ASymbol: ITemplateSymbol); begin - match(ASymbol.Token); + Match(ASymbol.Token); end; -function TTemplateParser.matchNumber(const ASymbol: TTemplateSymbol): extended; +function TTemplateParser.MatchNumber(const ASymbol: TTemplateSymbol): extended; begin - exit(StrToFloat(matchValue(ASymbol), FContext.FormatSettings)); + exit(StrToFloat(MatchValue(ASymbol), FContext.FormatSettings)); end; -function TTemplateParser.matchValue(const ASymbol: TTemplateSymbol): string; +function TTemplateParser.MatchValue(const ASymbol: TTemplateSymbol): string; var LSymbol: ITemplateSymbol; begin @@ -1887,9 +1892,9 @@ function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boo result := CurrentContainer; if AStream.Size > 0 then begin - ruleStmts(result, []); + RuleStmts(result, []); end; - match(vsEOF); + Match(vsEOF); result.Optimise; if eoPrettyPrint in FContext.Options then writeln(Template.PrettyPrint(result)); From 624cf5c68afd5cb2405afc3d3366679eb858c842 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 17:08:24 +0100 Subject: [PATCH 037/138] Add missing BlockResolver and BlockReplacer --- Sempare.Template.Pkg.dpk | 2 ++ Sempare.Template.Pkg.dproj | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index 17d3e71..c3a7fdd 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -64,6 +64,8 @@ requires dbrtl; contains + Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', + Sempare.Template.BlockReplacer in 'src\Sempare.Template.BlockReplacer.pas', Sempare.Template.Visitor in 'src\Sempare.Template.Visitor.pas', Sempare.Template.Util in 'src\Sempare.Template.Util.pas', Sempare.Template.StackFrame in 'src\Sempare.Template.StackFrame.pas', diff --git a/Sempare.Template.Pkg.dproj b/Sempare.Template.Pkg.dproj index e530430..93b40cf 100644 --- a/Sempare.Template.Pkg.dproj +++ b/Sempare.Template.Pkg.dproj @@ -90,7 +90,8 @@ - + + From 8718797817f7c3a058f75fdbcae690b6c828f486 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 30 Mar 2023 17:16:13 +0100 Subject: [PATCH 038/138] Update rail road image for 'for range' --- docs/images/stmt_for_range.svg | 282 ++++++++++++++++----------------- docs/rail-road-diagrams.txt | 2 - 2 files changed, 133 insertions(+), 151 deletions(-) diff --git a/docs/images/stmt_for_range.svg b/docs/images/stmt_for_range.svg index e6d8d76..10e07d7 100644 --- a/docs/images/stmt_for_range.svg +++ b/docs/images/stmt_for_range.svg @@ -1,4 +1,4 @@ - + @@ -11,224 +11,208 @@ - - + + - - - -<% + + + +<% - - + + - - - -for + + + +for - - + + - - - -var + + + +var - - + + - - - -:= + + + +:= - - + + - - - -expr + + + +expr - + - - - + + + - - - -to + + + +to - - + + - - - -downto + + + +downto - + - + - - - -expr + + + +expr - + - + - + - + - + - + - + - + - - - -step + + + +step - - - - - - -offset - - - - - - - -limit - - + - + - - - -expr + + + +expr - - + + - + - + - + - + - - - -%> + + + +%> - - + + - - - + + + - - - + + + - - - -block + + + +block - - + + - - - -<% continue %> + + + +<% continue %> - - + + - - - -<% break %> + + + +<% break %> - + - - + + - + - + - - + + - + - - + + - - - -<% + + + +<% - - + + - - - -end + + + +end - - + + - - - -%> + + + +%> - - + + - - + + '#$D#$A + // + ' Welcome to my Sempare'#$D#$A' '#$D#$A' '#$D#$A#$D#$A#$D#$A + // + '
'#$D#$A' '#$D#$A' '#$D#$A + // + ' '#$D#$A' '#$D#$A + // + ' '#$D#$A' '#$D#$A + // + ' '#$D#$A' '#$D#$A + // + ' '#$D#$A' '#$D#$A' '#$D#$A'
FirstName
LastName
Email
'#$D#$A' '#$D#$A + // + ' '#$D#$A' '#$D#$A + // + '
'#$D#$A'
'#$D#$A#$D#$A#$D#$A + // + '

Copyright (c) 2023

'#$D#$A' '#$D#$A''#$D#$A#$D#$A#$D#$A; + +procedure ThreadDetail(const AFailed: pinteger; const ANumEvals: integer; const ATemplate: ITemplate; const ATemplateData: TTemplateData); +var + LResult: string; + i: integer; + LStopWatch: TStopwatch; + LElapsedMs: double; +begin + + LStopWatch := TStopwatch.create; + LStopWatch.Start; + + for i := 0 to ANumEvals do + begin + try + LResult := Template.Eval(ATemplate, ATemplateData); + if ExpectedResult <> LResult then + begin + AtomicIncrement(AFailed^); + end; + except + AtomicIncrement(AFailed^); + end; + end; + LStopWatch.Stop; + LElapsedMs := LStopWatch.ElapsedMilliseconds / ANumEvals; + + if LElapsedMs > GetTestTimeTollerance(0.25, 6.0) then + AtomicIncrement(AFailed^); + +end; + +procedure TTestTemplateInclude.TestThreadedWebForm; var LTemplateData: TTemplateData; LTemplate: ITemplate; i: integer; - LThread: TThread; - LThreads: TObjectList; LNumThreads: integer; LNumEvals: integer; + LFailed: integer; + LEvent: TCountDownEvent; begin LTemplateData.company := 'Sempare'; LTemplateData.CopyrightYear := 2023; @@ -539,57 +582,33 @@ TTemplateData = record '<% block "body" %><% include("TForm") %><% end %> '#13#10 + // 36 '<% end %>'#13#10 // 37 ); - LThreads := TObjectList.create; - LNumThreads := min(1, CPUCount); - LNumEvals := 500; - for i := 0 to LNumThreads do - begin - LThread := TThread.CreateAnonymousThread( - procedure - var - LResult: string; - i: integer; - LStopWatch: TStopwatch; - LElapsedMs: double; - begin - LStopWatch := TStopwatch.create; - LStopWatch.Start; + LEvent := nil; + try + LNumThreads := min(1, CPUCount); + LEvent := TCountDownEvent.create(LNumThreads); + + LNumEvals := 500; + LFailed := 0; - for i := 0 to LNumEvals do + for i := 1 to LNumThreads do + begin + TThread.CreateAnonymousThread( + procedure begin - LResult := Template.Eval(LTemplate, LTemplateData); - Assert.AreEqual(#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10#13#10''#13#10' '#13#10' Welcome to my Sempare'#13#10 + // - ' '#13#10' '#13#10''#13#10''#13#10'
'#13#10 + // - ' '#13#10' '#13#10' '#13#10' '#13#10' '#13#10 + // - ' '#13#10' '#13#10' '#13#10 + // - ' '#13#10' '#13#10' '#13#10 + // - '
FirstName' + // - '
LastName
Email
'#13#10' '#13#10 + // - ' '#13#10' '#13#10'
'#13#10'
'#13#10''#13#10''#13#10'

Copyright (c) 2023

'#13#10' '#13#10''#13#10''#13#10''#13#10, LResult); - end; - LStopWatch.Stop; - LElapsedMs := LStopWatch.ElapsedMilliseconds / LNumEvals; - - Assert.IsTrue(LElapsedMs < 0.1400); - end); - LThread.FreeOnTerminate := false; - LThreads.Add(LThread); - end; - for LThread in LThreads do - begin - LThread.Start; - end; - for LThread in LThreads do - begin - LThread.WaitFor; + ThreadDetail(@LFailed, LNumEvals, LTemplate, LTemplateData); + LEvent.Signal(); + end).Start; + end; + LEvent.WaitFor(); + Assert.AreEqual(0, LFailed); + finally + LEvent.Free; end; end; procedure TTestTemplateInclude.TestTimedWebForm; - type - TTemplateData = record company: string; CopyrightYear: integer; @@ -672,9 +691,9 @@ TTemplateData = record end; LStopWatch.Stop; LElapsedMs := LStopWatch.ElapsedMilliseconds / LIterations; - - Assert.IsTrue(LElapsedMs < 0.1400); - +{$IF defined( WIN32) OR defined(WIN64)} + Assert.IsTrue(LElapsedMs <= GetTestTimeTollerance(0.15, 6.0)); +{$ENDIF} end; procedure TTestTemplateInclude.TestWebForm; From cfc569c358f5e3c520828efa08eeda02a19a2068 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 19:11:23 +0100 Subject: [PATCH 046/138] Update docs --- README.md | 9 ++-- docs/builtin-functions.md | 23 ++++++++- docs/template-patterns.md | 23 +++++++++ docs/whitespace-removal.md | 100 +++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 docs/whitespace-removal.md diff --git a/README.md b/README.md index 820240f..684ebc8 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ Open Source: https://github.com/sempare/sempare-delphi-template-engine 16. [Components](./docs/components.md) 17. [Tricks](./docs/tricks.md) 18. [Template Patterns](./docs/template-patterns.md) -19. [Internals](./docs/internals.md) -20. [Restrictions/Limitations/Known Bugs](./docs/restrictions.md) -21. [License](#License) +19. [Whitespace Removal](./docs/whitespace-removal.md) +20. [Internals](./docs/internals.md) +21. [Restrictions/Limitations/Known Bugs](./docs/restrictions.md) +22. [License](#License) ## Introduction @@ -169,7 +170,7 @@ Open __Sempare.Template.Engine.Group.groupproj__ which will include: - __Sempare.Template.Tester.dproj__ - 100+ unit tests + 160+ unit tests - __demo\VelocityDemo\Sempare.Template.Demo.dproj__ diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md index a07c3b9..0aec83b 100644 --- a/docs/builtin-functions.md +++ b/docs/builtin-functions.md @@ -39,7 +39,9 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) - [crnl](#crnl) - [nl](#nl) - [recordcount](#recordcount) - +- [min](#min) +- [max](#max) +- [abs](#abs) ## trim(string) Remove whitespace from a string. @@ -219,4 +221,21 @@ return a string with len #10 return the length of a dataset ``` <% RecordCount(ds) %> -``` \ No newline at end of file +``` + +## min(adouble, bdouble) +return the minimum of two values +``` +<% min(1,2) %> +``` + +## max(adouble, bdouble) +return the maximum of two values +``` +<% max(1,2) %> +``` +## abs(value) +return the absolute value of a value +``` +<% abs(-123.45) %> +``` diff --git a/docs/template-patterns.md b/docs/template-patterns.md index d82fcbf..6c90ec1 100644 --- a/docs/template-patterns.md +++ b/docs/template-patterns.md @@ -5,9 +5,32 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ## Template Patterns Here are some patterns that may be used when designing templates: +- Extends/Block - Header/Footer - Master/Detail +When you have many templates, you may want to leverage a ITemplateContext.TemplateResolver. + +### Extends/Block + +In this pattern, you may have a parent template parent.tpl: +``` +<% block 'body' %>some random content here<% end %> +``` + +In a child template: +``` +<% extends 'parent.tpl'%> + // this is ignored + <% block 'body' %>this content will appear in the body<% end %> + // this is ignored +<% end%> +``` + +Note: that the extends container is the only container where text outside of a block is ignored. + +When the child template is evaluated, it will replace all instances of 'body'. + ### Header/Footer In this pattern, you may have a set of templates like the following: diff --git a/docs/whitespace-removal.md b/docs/whitespace-removal.md new file mode 100644 index 0000000..e1b1057 --- /dev/null +++ b/docs/whitespace-removal.md @@ -0,0 +1,100 @@ +# ![](../images/sempare-logo-45px.png) Sempare Template Engine + +Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) + +## Whitespace Removal + +Removing whitespace is a tricky problem in templates. In the template engine, attempts have been made to address this in a number of ways. + +### Using script tag hints + +Scripts start with <% and close with %>. These tags may have additional hints to assist with the removal of whitespace. + +| Hint | Note | +|---|---| +| - | Removes whitespace only. | +| + | Removes whitespace only, but leaves one space. | +| * | Removes whitespace as well as a newline. | + +#### Using - + +``` +hello<%- 'world' %> +``` + +This yields: +``` +helloworld +``` + +This stops on the first non whitespace character or newline. The newline will be preserved. + +``` +hello<%- 'world' -%> +``` + +This yields: +``` +helloworld +``` + + +#### Using + + +``` +hello<%+ 'world' %> +``` + +This yields: +``` +helloworld +``` + +This stops on the first non whitespace character or newline. The newline will be preserved. + +``` +hello<%+ 'world' +%> +``` + +This yields: +``` +helloworld +``` + +#### Using * + +``` +hello<%+ 'world' *%> +``` + +This yields: +``` +helloworld +``` + +This stops on the first non whitespace character or including newline). The newline will be removed. + +#### Using around scripts with content + +``` +<%- if cond *%> +<% 'hello' %> +<%- end *%> +``` + +If cond is true, it evaluates to: +``` +hello +``` + +Note that content <NL> after *%> is part of the content rendered with the condition. + +Similar for before the <%- end *%>. + +This is important to appreciate especially when dealing with looping. + + +#### General notes + +- If you want to remove a script block from the output, use <%- *%> +- Use the template engine demo to play to see how it behaves. \ No newline at end of file From 5d9d54300410c3c4b1103995017cb61643f015cf Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 19:11:48 +0100 Subject: [PATCH 047/138] Add min/max/abs --- src/Sempare.Template.Functions.pas | 6 ++++++ tests/Sempare.Template.TestFunctions.pas | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Sempare.Template.Functions.pas b/src/Sempare.Template.Functions.pas index 665e92f..710372d 100644 --- a/src/Sempare.Template.Functions.pas +++ b/src/Sempare.Template.Functions.pas @@ -194,6 +194,7 @@ TInternalFuntions = class class function PadRight(const AStr: string; const ANum: integer; const APadChar: char): string; overload; static; class function Min(const AValue, BValue: double): double; static; class function Max(const AValue, BValue: double): double; static; + class function Abs(const AValue: double): double; static; {$IFDEF SEMPARE_TEMPLATE_FIREDAC} class function RecordCount(const ADataset: TDataSet): integer; static; {$ENDIF} @@ -584,6 +585,11 @@ class function TInternalFuntions.Base64Encode(const AStr: string): string; end; {$ENDIF} +class function TInternalFuntions.Abs(const AValue: double): double; +begin + exit(System.Abs(AValue)); +end; + class function TInternalFuntions.Bool(const AValue: TValue): boolean; begin exit(AsBoolean(AValue)); diff --git a/tests/Sempare.Template.TestFunctions.pas b/tests/Sempare.Template.TestFunctions.pas index 67b1d35..3c94ee5 100644 --- a/tests/Sempare.Template.TestFunctions.pas +++ b/tests/Sempare.Template.TestFunctions.pas @@ -115,6 +115,12 @@ TFunctionTest = class procedure HtmlEscape; [Test] procedure HtmlUnescape; + [Test] + procedure TestMin; + [Test] + procedure TestMax; + [Test] + procedure TestAbs; end; type @@ -514,6 +520,22 @@ procedure TFunctionTest.TestUppercase; Assert.AreEqual('HELLO', Template.Eval('<% uppercase(''HeLlo'') %>')); end; +procedure TFunctionTest.TestMin; +begin + Assert.AreEqual('1', Template.Eval('<% min(1,2) %>')); +end; + +procedure TFunctionTest.TestMax; +begin + Assert.AreEqual('2', Template.Eval('<% max(1,2) %>')); +end; + +procedure TFunctionTest.TestAbs; +begin + Assert.AreEqual('123.45', Template.Eval('<% abs(-123.45) %>')); + Assert.AreEqual('123.45', Template.Eval('<% abs(123.45) %>')); +end; + initialization TDUnitX.RegisterTestFixture(TFunctionTest); From c313aeef7ac023221c664e36fd9a00be60c4d830 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 20:58:10 +0100 Subject: [PATCH 048/138] Remove typo --- docs/whitespace-removal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whitespace-removal.md b/docs/whitespace-removal.md index e1b1057..6c0aedd 100644 --- a/docs/whitespace-removal.md +++ b/docs/whitespace-removal.md @@ -72,7 +72,7 @@ This yields: helloworld ``` -This stops on the first non whitespace character or including newline). The newline will be removed. +This stops on the first non whitespace character or including newline. The newline will be removed. #### Using around scripts with content From 7ec7048115023b8db27415219e03b379835be788 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 21:29:08 +0100 Subject: [PATCH 049/138] Fix test and whitespace docs --- docs/whitespace-removal.md | 4 ++-- src/Sempare.Template.Evaluate.pas | 2 +- tests/Sempare.Template.TestLexer.pas | 12 ++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/whitespace-removal.md b/docs/whitespace-removal.md index 6c0aedd..739c3c5 100644 --- a/docs/whitespace-removal.md +++ b/docs/whitespace-removal.md @@ -13,7 +13,7 @@ Scripts start with <% and close with %>. These tags may have additional hints to | Hint | Note | |---|---| | - | Removes whitespace only. | -| + | Removes whitespace only, but leaves one space. | +| + | Removes whitespace as well as a newline, but leaves one space. | | * | Removes whitespace as well as a newline. | #### Using - @@ -58,7 +58,7 @@ hello This yields: ``` -helloworld +helloworld ``` #### Using * diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 2c5b294..1848669 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -1354,7 +1354,7 @@ procedure TNewLineStreamWriter.Write(const AString: string); LStripAction := FStripStmt.Action; LHasLooped := false; LProcessed := 0; - while LIdx < length(AString) do + while LIdx <= length(AString) do begin LChar := AString[LIdx]; if charinset(LChar, STRIP_CHARS[LStripAction]) then diff --git a/tests/Sempare.Template.TestLexer.pas b/tests/Sempare.Template.TestLexer.pas index 0966ad6..2e07ce7 100644 --- a/tests/Sempare.Template.TestLexer.pas +++ b/tests/Sempare.Template.TestLexer.pas @@ -65,6 +65,8 @@ TTestTemplateLexer = class procedure TestInvalidChar; [Test] procedure TestStripCharLeftAndRight; + [Test] + procedure TestStripWhitespace; end; implementation @@ -174,6 +176,16 @@ procedure TTestTemplateLexer.TestStripCharLeftAndRight; Assert.AreEqual('-123', Template.Eval('<%- -123%>')); end; +procedure TTestTemplateLexer.TestStripWhitespace; +begin + Assert.AreEqual('helloworld '#13#10, Template.Eval('hello <%- "world" %> '#13#10)); + Assert.AreEqual('helloworld'#13#10, Template.Eval('hello <%- "world" -%> '#13#10)); + + Assert.AreEqual('hello world '#13#10, Template.Eval('hello <%+ "world" %> '#13#10)); + Assert.AreEqual('hello world ', { . . } Template.Eval('hello <%+ "world" +%> '#13#10)); + Assert.AreEqual('hello world', { . . } Template.Eval('hello <%+ "world" *%> '#13#10)); +end; + procedure TTestTemplateLexer.TestUnicodeQuotedString; begin Assert.AreEqual('this is a test', Template.Eval('<% ‘this is a test’ %>')); From 3f7d3975588b520a6c10c26f272aa8df8adfec16 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 23:10:03 +0100 Subject: [PATCH 050/138] initial WebBroker Standalone demo --- demo/WebBrokerStandalone/DynForm.pas | 42 + demo/WebBrokerStandalone/ServerConst1.pas | 37 + demo/WebBrokerStandalone/TemplateRegistry.pas | 116 ++ .../WebBrokerStandalone8080.dpr | 159 +++ .../WebBrokerStandalone8080.dproj | 988 ++++++++++++++++++ demo/WebBrokerStandalone/WebModuleUnit1.dfm | 16 + demo/WebBrokerStandalone/WebModuleUnit1.pas | 49 + demo/WebBrokerStandalone/template.rc | 4 + .../WebBrokerStandalone/templates/dynform.tpl | 19 + .../templates/helper_template.tpl | 23 + demo/WebBrokerStandalone/templates/index.tpl | 13 + .../templates/main_template.tpl | 15 + 12 files changed, 1481 insertions(+) create mode 100644 demo/WebBrokerStandalone/DynForm.pas create mode 100644 demo/WebBrokerStandalone/ServerConst1.pas create mode 100644 demo/WebBrokerStandalone/TemplateRegistry.pas create mode 100644 demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr create mode 100644 demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj create mode 100644 demo/WebBrokerStandalone/WebModuleUnit1.dfm create mode 100644 demo/WebBrokerStandalone/WebModuleUnit1.pas create mode 100644 demo/WebBrokerStandalone/template.rc create mode 100644 demo/WebBrokerStandalone/templates/dynform.tpl create mode 100644 demo/WebBrokerStandalone/templates/helper_template.tpl create mode 100644 demo/WebBrokerStandalone/templates/index.tpl create mode 100644 demo/WebBrokerStandalone/templates/main_template.tpl diff --git a/demo/WebBrokerStandalone/DynForm.pas b/demo/WebBrokerStandalone/DynForm.pas new file mode 100644 index 0000000..736f8bc --- /dev/null +++ b/demo/WebBrokerStandalone/DynForm.pas @@ -0,0 +1,42 @@ +unit DynForm; + +interface + +type + TField = record + Caption: string; + Name: string; + FieldType: string; + constructor create(const ACaption, AName: string; const AFieldType: string = 'TEdit'); + end; + + TButton = record + Caption: string; + Name: string; + constructor create(const ACaption, AName: string); + end; + + TTemplateData = record + Title : string; + FormName: string; + FormAction: string; + Fields: TArray; + Buttons: TArray; + end; + +implementation + +constructor TField.create(const ACaption, AName, AFieldType: string); +begin + Caption := ACaption; + name := AName; + FieldType := AFieldType; +end; + +constructor TButton.create(const ACaption, AName: string); +begin + Caption := ACaption; + name := AName; +end; + +end. diff --git a/demo/WebBrokerStandalone/ServerConst1.pas b/demo/WebBrokerStandalone/ServerConst1.pas new file mode 100644 index 0000000..195a95a --- /dev/null +++ b/demo/WebBrokerStandalone/ServerConst1.pas @@ -0,0 +1,37 @@ +unit ServerConst1; + +interface + +resourcestring + sPortInUse = '- Error: Port %s already in use'; + sPortSet = '- Port set to %s'; + sServerRunning = '- The Server is already running'; + sStartingServer = '- Starting HTTP Server on port %d'; + sStoppingServer = '- Stopping Server'; + sServerStopped = '- Server Stopped'; + sServerNotRunning = '- The Server is not running'; + sInvalidCommand = '- Error: Invalid Command'; + sIndyVersion = '- Indy Version: '; + sActive = '- Active: '; + sPort = '- Port: '; + sSessionID = '- Session ID CookieName: '; + sCommands = 'Enter a Command: ' + slineBreak + + ' - "start" to start the server'+ slineBreak + + ' - "stop" to stop the server'+ slineBreak + + ' - "set port" to change the default port'+ slineBreak + + ' - "status" for Server status'+ slineBreak + + ' - "help" to show commands'+ slineBreak + + ' - "exit" to close the application'; + +const + cArrow = '->'; + cCommandStart = 'start'; + cCommandStop = 'stop'; + cCommandStatus = 'status'; + cCommandHelp = 'help'; + cCommandSetPort = 'set port'; + cCommandExit = 'exit'; + +implementation + +end. diff --git a/demo/WebBrokerStandalone/TemplateRegistry.pas b/demo/WebBrokerStandalone/TemplateRegistry.pas new file mode 100644 index 0000000..f47b5c5 --- /dev/null +++ b/demo/WebBrokerStandalone/TemplateRegistry.pas @@ -0,0 +1,116 @@ +unit TemplateRegistry; + +interface + +uses + System.SysUtils, + System.Classes, + System.Types, + System.Generics.Collections, + System.SyncObjs, + Sempare.Template; + +type + TTemplateRegistry = class + private + class var FTemplateRegistry: TTemplateRegistry; + class constructor Create; + class destructor Destroy; + private + FLock: TCriticalSection; + FTemplates: TDictionary; + FContext: ITemplateContext; + public + constructor Create; + destructor Destroy; override; + function GetTemplate(const ATemplateName: string): ITemplate; + function ProcessTemplate(const ATemplateName: string; const AData: T): string; overload; + function ProcessTemplate(const ATemplateName: string): string; overload; + class property Instance: TTemplateRegistry read FTemplateRegistry; + end; + +implementation + +uses + System.DateUtils; + +{ TTemplateRegistry } + +constructor TTemplateRegistry.Create; +begin + FLock := TCriticalSection.Create; + FTemplates := TDictionary.Create; + FContext := Template.Context([eoEmbedException]); + + FContext.Variable['Company'] := 'My Company - Sempare Limited'; + FContext.Variable['CopyrightYear'] := YearOf(Now); + + FContext.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate + begin + exit(GetTemplate(AName)); + end; +end; + +class constructor TTemplateRegistry.Create; +begin + FTemplateRegistry := TTemplateRegistry.Create; +end; + +destructor TTemplateRegistry.Destroy; +begin + FLock.Free; + FTemplates.Free; + inherited; +end; + +class destructor TTemplateRegistry.Destroy; +begin + FTemplateRegistry.Free; +end; + +function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; +var + LStream: TResourceStream; +begin + FLock.Acquire; + try + if FTemplates.TryGetValue(ATemplateName, result) then + exit; + finally + FLock.Release; + end; + LStream := TResourceStream.Create(HInstance, ATemplateName.tolower, RT_RCDATA); + try + result := Template.Parse(FContext, LStream); + except + on e: exception do + begin + result := Template.Parse(e.Message); + exit; + end; + end; + FLock.Acquire; + try + FTemplates.Add(ATemplateName, result); + finally + FLock.Release; + end; +end; + +function TTemplateRegistry.ProcessTemplate(const ATemplateName: string): string; +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + exit(Template.Eval(FContext, LTemplate)); +end; + +function TTemplateRegistry.ProcessTemplate(const ATemplateName: string; const AData: T): string; +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + exit(Template.Eval(FContext, LTemplate, AData)); +end; + +end. diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr new file mode 100644 index 0000000..00e90b9 --- /dev/null +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr @@ -0,0 +1,159 @@ +program WebBrokerStandalone8080; +{$APPTYPE CONSOLE} +{$R 'template.res' 'template.rc'} + +uses + System.SysUtils, + System.Types, + IPPeerServer, + IPPeerAPI, + IdHTTPWebBrokerBridge, + Web.WebReq, + Web.WebBroker, + WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule}, + ServerConst1 in 'ServerConst1.pas', + TemplateRegistry in 'TemplateRegistry.pas', + DynForm in 'DynForm.pas'; + +{$R *.res} +{$R template.res} + +function BindPort(APort: Integer): Boolean; +var + LTestServer: IIPTestServer; +begin + Result := True; + try + LTestServer := PeerFactory.CreatePeer('', IIPTestServer) as IIPTestServer; + LTestServer.TestOpenPort(APort, nil); + except + Result := False; + end; +end; + +function CheckPort(APort: Integer): Integer; +begin + if BindPort(APort) then + Result := APort + else + Result := 0; +end; + +procedure SetPort(const AServer: TIdHTTPWebBrokerBridge; APort: String); +begin + if not AServer.Active then + begin + APort := APort.Replace(cCommandSetPort, '').Trim; + if CheckPort(APort.ToInteger) > 0 then + begin + AServer.DefaultPort := APort.ToInteger; + Writeln(Format(sPortSet, [APort])); + end + else + Writeln(Format(sPortInUse, [APort])); + end + else + Writeln(sServerRunning); + Write(cArrow); +end; + +procedure StartServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if not AServer.Active then + begin + if CheckPort(AServer.DefaultPort) > 0 then + begin + Writeln(Format(sStartingServer, [AServer.DefaultPort])); + AServer.Bindings.Clear; + AServer.Active := True; + end + else + Writeln(Format(sPortInUse, [AServer.DefaultPort.ToString])); + end + else + Writeln(sServerRunning); + Write(cArrow); +end; + +procedure StopServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if AServer.Active then + begin + Writeln(sStoppingServer); + AServer.Active := False; + AServer.Bindings.Clear; + Writeln(sServerStopped); + end + else + Writeln(sServerNotRunning); + Write(cArrow); +end; + +procedure WriteCommands; +begin + Writeln(sCommands); + Write(cArrow); +end; + +procedure WriteStatus(const AServer: TIdHTTPWebBrokerBridge); +begin + Writeln(sIndyVersion + AServer.SessionList.Version); + Writeln(sActive + AServer.Active.ToString(TUseBoolStrs.True)); + Writeln(sPort + AServer.DefaultPort.ToString); + Writeln(sSessionID + AServer.SessionIDCookieName); + Write(cArrow); +end; + +procedure RunServer(APort: Integer); +var + LServer: TIdHTTPWebBrokerBridge; + LResponse: string; +begin + WriteCommands; + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.DefaultPort := APort; + while True do + begin + Readln(LResponse); + LResponse := LowerCase(LResponse); + if LResponse.StartsWith(cCommandSetPort) then + SetPort(LServer, LResponse) + else if sametext(LResponse, cCommandStart) then + StartServer(LServer) + else if sametext(LResponse, cCommandStatus) then + WriteStatus(LServer) + else if sametext(LResponse, cCommandStop) then + StopServer(LServer) + else if sametext(LResponse, cCommandHelp) then + WriteCommands + else if sametext(LResponse, cCommandExit) then + if LServer.Active then + begin + StopServer(LServer); + break + end + else + break + else + begin + Writeln(sInvalidCommand); + Write(cArrow); + end; + end; + finally + LServer.Free; + end; +end; + +begin + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + RunServer(8080); + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end + +end. diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj new file mode 100644 index 0000000..22edc77 --- /dev/null +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj @@ -0,0 +1,988 @@ + + + {40CEFD19-2AD2-4989-9F55-4C51364DAF9E} + 19.5 + None + True + Debug + Win32 + 129 + Console + WebBrokerStandalone8080.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + WebBrokerStandalone8080 + ..\..\src;$(DCC_UnitSearchPath) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + activity-1.1.0.dex.jar;annotation-1.2.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;biometric-1.1.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.1.0.dex.jar;core-runtime-2.1.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.2.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.2.0.dex.jar;lifecycle-runtime-2.2.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.2.0.dex.jar;lifecycle-viewmodel-savedstate-2.2.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;savedstate-1.0.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + activity-1.1.0.dex.jar;annotation-1.2.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;biometric-1.1.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.1.0.dex.jar;core-runtime-2.1.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.2.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.2.0.dex.jar;lifecycle-runtime-2.2.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.2.0.dex.jar;lifecycle-viewmodel-savedstate-2.2.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;savedstate-1.0.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + + + DataSnapServer;FlexCel_Report;fmx;emshosting;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;FMXTMSFNCWebSocketPkgDXE14;emsedge;FlexCel_Pdf;SKIA_FlexCel_Core;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;FMXTMSFNCWXPackPkgDXE14;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;FlexCel_XlsAdapter;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;FlexCel_Core;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + Debug + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + Debug + + + vclwinx;DataSnapServer;Sempare.IDE.DebugConfigurations.Pkg;FlexCel_Report;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;appanalytics;IndyProtocols;vclx;FMX_FlexCel_Components;IndyIPClient;dbxcds;vcledge;VCLTMSFNCWXPackPkgDXE14;bindcompvclwinx;FmxTeeUI;VCLTMSFNCUIPackPkgDXE14;FMXTMSFNCWebSocketPkgDXE14;emsedge;bindcompfmx;FlexCel_Pdf;DBXFirebirdDriver;VCLTMSFNCWebSocketPkgDXE14;SKIA_FlexCel_Core;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FMXTMSFNCWXPackPkgDXE14;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;TMSWEBCorePkgLibDXE14;DBXOdbcDriver;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FireDACOracleDriver;TMSWEBCorePkgDXE14;fmxdae;TeeDB;FMXTMSFNCBloxPkgDXE14;FlexCel_XlsAdapter;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;VCLTMSFNCCorePkgDXE14;vcldb;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;vclFireDAC;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;VCLTMSFNCBloxPkgDXE14;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + vclwinx;DataSnapServer;FlexCel_Report;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;appanalytics;IndyProtocols;vclx;FMX_FlexCel_Components;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;VCLTMSFNCUIPackPkgDXE14;emsedge;bindcompfmx;FlexCel_Pdf;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;DBXOdbcDriver;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;FlexCel_XlsAdapter;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;VCLTMSFNCCorePkgDXE14;vcldb;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;vclFireDAC;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + 1033 + (None) + none + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + +
template.res
+
+ +
WebModule1
+ dfm + TWebModule +
+ + + + + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Console + + + + WebBrokerStandalone8080.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + (untitled) + + + + + + true + + + + + true + + + + + true + + + + + WebBrokerStandalone8080.exe + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + 1 + + + 0 + + + + + classes + 64 + + + classes + 64 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + False + False + False + False + True + False + False + True + False + + + 12 + + + + +
diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.dfm b/demo/WebBrokerStandalone/WebModuleUnit1.dfm new file mode 100644 index 0000000..ee9a541 --- /dev/null +++ b/demo/WebBrokerStandalone/WebModuleUnit1.dfm @@ -0,0 +1,16 @@ +object WebModule1: TWebModule1 + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + OnAction = WebModule1DefaultHandlerAction + end + item + Name = 'FormInput' + PathInfo = '/form' + OnAction = WebModule1FormInputAction + end> + Height = 230 + Width = 415 +end diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas new file mode 100644 index 0000000..bbe9bed --- /dev/null +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -0,0 +1,49 @@ +unit WebModuleUnit1; + +interface + +uses + System.SysUtils, System.Classes, Web.HTTPApp; + +type + TWebModule1 = class(TWebModule) + procedure WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure WebModule1FormInputAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + private + { Private declarations } + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TWebModule1; + +implementation + +uses + DynForm, + TemplateRegistry; + +{%CLASSGROUP 'System.Classes.TPersistent'} +{$R *.dfm} + +procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + Response.Content := TTemplateRegistry.Instance.ProcessTemplate('index'); + Handled := true; +end; + +procedure TWebModule1.WebModule1FormInputAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +var + LTemplateData: TTemplateData; +begin + LTemplateData.Title := 'User Details'; + LTemplateData.FormName := 'userinfo'; + LTemplateData.FormAction := '/userinfo'; + LTemplateData.Fields := [TField.create('FirstName', 'firstname'), TField.create('LastName', 'lastname'), TField.create('Email', 'email', 'TEmail')]; + LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; + Response.Content := TTemplateRegistry.Instance.ProcessTemplate('dynform', LTemplateData); + Handled := true; +end; + +end. diff --git a/demo/WebBrokerStandalone/template.rc b/demo/WebBrokerStandalone/template.rc new file mode 100644 index 0000000..9d5ce69 --- /dev/null +++ b/demo/WebBrokerStandalone/template.rc @@ -0,0 +1,4 @@ +index RCDATA "templates\\index.tpl" +dynform RCDATA "templates\\dynform.tpl" +helper RCDATA "templates\\helper_template.tpl" +template RCDATA "templates\\main_template.tpl" diff --git a/demo/WebBrokerStandalone/templates/dynform.tpl b/demo/WebBrokerStandalone/templates/dynform.tpl new file mode 100644 index 0000000..097063e --- /dev/null +++ b/demo/WebBrokerStandalone/templates/dynform.tpl @@ -0,0 +1,19 @@ +<% include ('helper') %> + +<% extends ("template") %> + <% block "header" *%> + + + Welcome to my <% Company %> + + + <% end *%> + + <% block "body" %><% include("TForm") %><% end %> + + <% block "footer" %> +

Copyright (c) <% CopyrightYear %> <% Company %>

+ + + <% end %> +<% end %> diff --git a/demo/WebBrokerStandalone/templates/helper_template.tpl b/demo/WebBrokerStandalone/templates/helper_template.tpl new file mode 100644 index 0000000..44abcca --- /dev/null +++ b/demo/WebBrokerStandalone/templates/helper_template.tpl @@ -0,0 +1,23 @@ +<% template "TEdit" %><% Caption %><% end %> + +<% template "TEmail" %><% Caption %><% end %> + +<% template "TButton" %><% end %> + +<% template "TForm" %> +

<% Title %>

+
+ + <% for field of fields %> + <% include(field.FieldType, field)%> + <% end %> + + + +
+ <% for button of buttons %> + <% include("TButton", button) %> + <% end %> +
+
+<% end %> \ No newline at end of file diff --git a/demo/WebBrokerStandalone/templates/index.tpl b/demo/WebBrokerStandalone/templates/index.tpl new file mode 100644 index 0000000..d64a351 --- /dev/null +++ b/demo/WebBrokerStandalone/templates/index.tpl @@ -0,0 +1,13 @@ +<% extends ( 'template' ) %> + <%- block 'body' *%> + +

+ Here are some numbers from 1 to 10: +

+
    + <%- for i := 1 to 10 *%> +
  • <% i %>
  • + <%- end *%> +
+ <%- end *%> +<% end *%> diff --git a/demo/WebBrokerStandalone/templates/main_template.tpl b/demo/WebBrokerStandalone/templates/main_template.tpl new file mode 100644 index 0000000..1281550 --- /dev/null +++ b/demo/WebBrokerStandalone/templates/main_template.tpl @@ -0,0 +1,15 @@ +<% block 'header' *%> + + + Sempare Web Demo on WebBroker + + +<% end *%> + +<% block 'body' *%> +<% end *%> + +<% block 'footer' *%> + + +<% end *%> \ No newline at end of file From debca0b3cf3a4d04cb239f2c464c8fb429b7b366 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 23:20:20 +0100 Subject: [PATCH 051/138] Update for WebBrokerStandalone8080 demo --- Sempare.Template.Engine.Group.groupproj | 18 ++++++++++--- demo/WebBrokerStandalone/README.md | 34 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 demo/WebBrokerStandalone/README.md diff --git a/Sempare.Template.Engine.Group.groupproj b/Sempare.Template.Engine.Group.groupproj index 0c7995e..c69865d 100644 --- a/Sempare.Template.Engine.Group.groupproj +++ b/Sempare.Template.Engine.Group.groupproj @@ -12,6 +12,9 @@ + + + Default.Personality.12 @@ -47,14 +50,23 @@ + + + + + + + + + - + - + - + diff --git a/demo/WebBrokerStandalone/README.md b/demo/WebBrokerStandalone/README.md new file mode 100644 index 0000000..4c08ac4 --- /dev/null +++ b/demo/WebBrokerStandalone/README.md @@ -0,0 +1,34 @@ +# WebBroker Standalone Demo + +# Running the Demo + +1. Open WebBrokerStandalone8080.dproj +2. In the IDE, run (F9) +3. In the console that starts up, type: start +4. In a browser, goto http://localhost:8080 + +# Templates directory + +The templates directory has a few template files defined. + +You need to pay attention to template.rc. The templates use names within the resource file, and not what is on the filesystem. + +# Helper units + +- DynForm.pas - example utility functionality +- TemplateRegistry.pas - utility functionality to load templates from the application resources + +# WebModuleUnit1.pas + +This is the web module that has been slightly modified for the example. + +It uses DynForm and TemplateRegistry. + +You should observe that templates are loaded using: + TTemplateRegistry.Instance.ProcessTemplate(templatename, data); + +# Limitations + +This demo shows how to use resources that are statically compiled into the application. + +Contact info@sempare.ltd if you need information or training. \ No newline at end of file From 33a779e05021fcfb26f12e2ad60848a5ab40b89c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Fri, 31 Mar 2023 23:23:43 +0100 Subject: [PATCH 052/138] Add README.md to WebBrokerStandalone8080.dproj --- demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj index 22edc77..5564b3e 100644 --- a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj @@ -172,6 +172,7 @@ + Base @@ -214,6 +215,12 @@ true + + + .\ + true + + WebBrokerStandalone8080.exe @@ -226,7 +233,7 @@ true - + .\ true From 822ff0b319416126c94c5fa3bb725fadceab0713 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sat, 1 Apr 2023 12:13:27 +0100 Subject: [PATCH 053/138] Added extras to demo app for form submission and error page --- demo/WebBrokerStandalone/DynForm.pas | 16 +++++--- demo/WebBrokerStandalone/WebModuleUnit1.dfm | 17 +++++++- demo/WebBrokerStandalone/WebModuleUnit1.pas | 41 +++++++++++++++++-- demo/WebBrokerStandalone/template.rc | 2 + demo/WebBrokerStandalone/templates/404.tpl | 11 +++++ .../templates/helper_template.tpl | 6 ++- demo/WebBrokerStandalone/templates/index.tpl | 2 + .../templates/submitted.tpl | 15 +++++++ src/Sempare.Template.Functions.pas | 12 ++++++ 9 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 demo/WebBrokerStandalone/templates/404.tpl create mode 100644 demo/WebBrokerStandalone/templates/submitted.tpl diff --git a/demo/WebBrokerStandalone/DynForm.pas b/demo/WebBrokerStandalone/DynForm.pas index 736f8bc..ab0e8d1 100644 --- a/demo/WebBrokerStandalone/DynForm.pas +++ b/demo/WebBrokerStandalone/DynForm.pas @@ -13,30 +13,36 @@ TField = record TButton = record Caption: string; Name: string; - constructor create(const ACaption, AName: string); + FieldType: string; + constructor create(const ACaption, AName: string; const AFieldType: string = 'TSubmitButton'); end; TTemplateData = record - Title : string; + Title: string; FormName: string; FormAction: string; Fields: TArray; Buttons: TArray; end; + TFormData = record + firstname, lastname, email: string; + end; + implementation constructor TField.create(const ACaption, AName, AFieldType: string); begin Caption := ACaption; - name := AName; + Name := AName; FieldType := AFieldType; end; -constructor TButton.create(const ACaption, AName: string); +constructor TButton.create(const ACaption, AName: string; const AFieldType: string); begin Caption := ACaption; - name := AName; + Name := AName; + FieldType := AFieldType; end; end. diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.dfm b/demo/WebBrokerStandalone/WebModuleUnit1.dfm index ee9a541..a5e24a7 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.dfm +++ b/demo/WebBrokerStandalone/WebModuleUnit1.dfm @@ -2,14 +2,27 @@ object WebModule1: TWebModule1 Actions = < item Default = True - Name = 'DefaultHandler' + Name = 'ErrorHandler' + PathInfo = '/404' + OnAction = WebModule1ErrorHandlerAction + end + item + MethodType = mtGet + Name = 'IndexHandler' PathInfo = '/' - OnAction = WebModule1DefaultHandlerAction + OnAction = WebModule1IndexHandlerAction end item + MethodType = mtGet Name = 'FormInput' PathInfo = '/form' OnAction = WebModule1FormInputAction + end + item + MethodType = mtPost + Name = 'FormInputHandler' + PathInfo = '/form' + OnAction = WebModule1FormInputHandlerAction end> Height = 230 Width = 415 diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas index bbe9bed..39b1199 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.pas +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -7,8 +7,10 @@ interface type TWebModule1 = class(TWebModule) - procedure WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure WebModule1IndexHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModule1FormInputAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure WebModule1FormInputHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure WebModule1ErrorHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public @@ -27,23 +29,54 @@ implementation {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} -procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +procedure TWebModule1.WebModule1IndexHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin Response.Content := TTemplateRegistry.Instance.ProcessTemplate('index'); Handled := true; end; +procedure TWebModule1.WebModule1ErrorHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + Response.Content := TTemplateRegistry.Instance.ProcessTemplate('error404'); + Response.StatusCode := 404; + Handled := true; +end; + procedure TWebModule1.WebModule1FormInputAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LTemplateData: TTemplateData; begin LTemplateData.Title := 'User Details'; LTemplateData.FormName := 'userinfo'; - LTemplateData.FormAction := '/userinfo'; - LTemplateData.Fields := [TField.create('FirstName', 'firstname'), TField.create('LastName', 'lastname'), TField.create('Email', 'email', 'TEmail')]; + LTemplateData.FormAction := Request.PathInfo; + LTemplateData.Fields := [ // + TField.create('FirstName', 'firstname'), // + TField.create('LastName', 'lastname'), // + TField.create('Email', 'email', 'TEmail') // + ]; LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; Response.Content := TTemplateRegistry.Instance.ProcessTemplate('dynform', LTemplateData); Handled := true; end; +procedure TWebModule1.WebModule1FormInputHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +var + LFormData: TFormData; + PostData: string; + Params: TStrings; +begin + PostData := Request.Content; + Params := TStringList.create; + try + ExtractStrings(['&'], [], PChar(PostData), Params); + LFormData.firstname := Params.Values['firstname']; + LFormData.lastname := Params.Values['lastname']; + LFormData.email := Params.Values['email']; + Response.Content := TTemplateRegistry.Instance.ProcessTemplate('submitted', LFormData); + finally + Params.Free; + end; + Handled := true; +end; + end. diff --git a/demo/WebBrokerStandalone/template.rc b/demo/WebBrokerStandalone/template.rc index 9d5ce69..5429538 100644 --- a/demo/WebBrokerStandalone/template.rc +++ b/demo/WebBrokerStandalone/template.rc @@ -2,3 +2,5 @@ index RCDATA "templates\\index.tpl" dynform RCDATA "templates\\dynform.tpl" helper RCDATA "templates\\helper_template.tpl" template RCDATA "templates\\main_template.tpl" +submitted RCDATA "templates\\submitted.tpl" +error404 RCDATA "templates\\404.tpl" diff --git a/demo/WebBrokerStandalone/templates/404.tpl b/demo/WebBrokerStandalone/templates/404.tpl new file mode 100644 index 0000000..ba754e2 --- /dev/null +++ b/demo/WebBrokerStandalone/templates/404.tpl @@ -0,0 +1,11 @@ +<% extends ("template") %> + + <% block "body" %> + +

Sorry, 404!

+ +

The page you tried to reference does not exist.

+ + <% end %> + +<% end %> diff --git a/demo/WebBrokerStandalone/templates/helper_template.tpl b/demo/WebBrokerStandalone/templates/helper_template.tpl index 44abcca..055df38 100644 --- a/demo/WebBrokerStandalone/templates/helper_template.tpl +++ b/demo/WebBrokerStandalone/templates/helper_template.tpl @@ -4,6 +4,10 @@ <% template "TButton" %><% end %> +<% template "TResetButton" %><% end %> + +<% template "TSubmitButton" %><% end %> + <% template "TForm" %>

<% Title %>

@@ -14,7 +18,7 @@ <% for button of buttons %> - <% include("TButton", button) %> + <% include(button.FieldType, button) %> <% end %> diff --git a/demo/WebBrokerStandalone/templates/index.tpl b/demo/WebBrokerStandalone/templates/index.tpl index d64a351..67ca8ea 100644 --- a/demo/WebBrokerStandalone/templates/index.tpl +++ b/demo/WebBrokerStandalone/templates/index.tpl @@ -9,5 +9,7 @@
  • <% i %>
  • <%- end *%> + example form + random eror 404 <%- end *%> <% end *%> diff --git a/demo/WebBrokerStandalone/templates/submitted.tpl b/demo/WebBrokerStandalone/templates/submitted.tpl new file mode 100644 index 0000000..d3f40a7 --- /dev/null +++ b/demo/WebBrokerStandalone/templates/submitted.tpl @@ -0,0 +1,15 @@ +<% extends ("template") %> + + <% block "body" %> + +

    The info:

    + + + + + +
    First Name<% firstname %>
    Last Name<% lastname %>
    E-mail<% FormDecode(email) %>
    + + <% end %> + +<% end %> diff --git a/src/Sempare.Template.Functions.pas b/src/Sempare.Template.Functions.pas index 710372d..3855982 100644 --- a/src/Sempare.Template.Functions.pas +++ b/src/Sempare.Template.Functions.pas @@ -203,6 +203,8 @@ TInternalFuntions = class class function Base64Decode(const AStr: string): string; static; class function HtmlUnescape(const AStr: string): string; static; class function HtmlEscape(const AStr: string): string; static; + class function UrlDecode(const AStr: string): string; static; + class function FormDecode(const AStr: string): string; static; {$ENDIF} {$IFDEF SUPPORT_HASH} class function Md5(const AStr: string): string; static; @@ -569,6 +571,16 @@ class function TInternalFuntions.HtmlEscape(const AStr: string): string; exit(TNetEncoding.HTML.Encode(AStr)); end; +class function TInternalFuntions.FormDecode(const AStr: string): string; +begin + exit(TNetEncoding.URL.FormDecode(AStr)); +end; + +class function TInternalFuntions.UrlDecode(const AStr: string): string; +begin + exit(TNetEncoding.URL.UrlDecode(AStr)); +end; + class function TInternalFuntions.HtmlUnescape(const AStr: string): string; begin exit(TNetEncoding.HTML.Decode(AStr)); From ea0b0564279c672db409a8477433202c1f8243c0 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 14:43:36 +0100 Subject: [PATCH 054/138] Refactor WebBroker example --- demo/WebBrokerStandalone/TemplateRegistry.pas | 116 ---- .../WebBrokerStandalone8080.dpr | 3 - .../WebBrokerStandalone8080.dproj | 22 +- .../WebBrokerStandalone8080.rc | 6 + demo/WebBrokerStandalone/WebModuleUnit1.pas | 2 +- demo/WebBrokerStandalone/template.rc | 6 - .../templates/{404.tpl => error404.tpl} | 0 .../{helper_template.tpl => helper.tpl} | 0 demo/WebBrokerStandalone/templates/index.tpl | 2 +- .../{main_template.tpl => template.tpl} | 0 src/Sempare.Template.TemplateRegistry.pas | 497 ++++++++++++++++++ 11 files changed, 519 insertions(+), 135 deletions(-) delete mode 100644 demo/WebBrokerStandalone/TemplateRegistry.pas create mode 100644 demo/WebBrokerStandalone/WebBrokerStandalone8080.rc delete mode 100644 demo/WebBrokerStandalone/template.rc rename demo/WebBrokerStandalone/templates/{404.tpl => error404.tpl} (100%) rename demo/WebBrokerStandalone/templates/{helper_template.tpl => helper.tpl} (100%) rename demo/WebBrokerStandalone/templates/{main_template.tpl => template.tpl} (100%) create mode 100644 src/Sempare.Template.TemplateRegistry.pas diff --git a/demo/WebBrokerStandalone/TemplateRegistry.pas b/demo/WebBrokerStandalone/TemplateRegistry.pas deleted file mode 100644 index f47b5c5..0000000 --- a/demo/WebBrokerStandalone/TemplateRegistry.pas +++ /dev/null @@ -1,116 +0,0 @@ -unit TemplateRegistry; - -interface - -uses - System.SysUtils, - System.Classes, - System.Types, - System.Generics.Collections, - System.SyncObjs, - Sempare.Template; - -type - TTemplateRegistry = class - private - class var FTemplateRegistry: TTemplateRegistry; - class constructor Create; - class destructor Destroy; - private - FLock: TCriticalSection; - FTemplates: TDictionary; - FContext: ITemplateContext; - public - constructor Create; - destructor Destroy; override; - function GetTemplate(const ATemplateName: string): ITemplate; - function ProcessTemplate(const ATemplateName: string; const AData: T): string; overload; - function ProcessTemplate(const ATemplateName: string): string; overload; - class property Instance: TTemplateRegistry read FTemplateRegistry; - end; - -implementation - -uses - System.DateUtils; - -{ TTemplateRegistry } - -constructor TTemplateRegistry.Create; -begin - FLock := TCriticalSection.Create; - FTemplates := TDictionary.Create; - FContext := Template.Context([eoEmbedException]); - - FContext.Variable['Company'] := 'My Company - Sempare Limited'; - FContext.Variable['CopyrightYear'] := YearOf(Now); - - FContext.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate - begin - exit(GetTemplate(AName)); - end; -end; - -class constructor TTemplateRegistry.Create; -begin - FTemplateRegistry := TTemplateRegistry.Create; -end; - -destructor TTemplateRegistry.Destroy; -begin - FLock.Free; - FTemplates.Free; - inherited; -end; - -class destructor TTemplateRegistry.Destroy; -begin - FTemplateRegistry.Free; -end; - -function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; -var - LStream: TResourceStream; -begin - FLock.Acquire; - try - if FTemplates.TryGetValue(ATemplateName, result) then - exit; - finally - FLock.Release; - end; - LStream := TResourceStream.Create(HInstance, ATemplateName.tolower, RT_RCDATA); - try - result := Template.Parse(FContext, LStream); - except - on e: exception do - begin - result := Template.Parse(e.Message); - exit; - end; - end; - FLock.Acquire; - try - FTemplates.Add(ATemplateName, result); - finally - FLock.Release; - end; -end; - -function TTemplateRegistry.ProcessTemplate(const ATemplateName: string): string; -var - LTemplate: ITemplate; -begin - LTemplate := GetTemplate(ATemplateName); - exit(Template.Eval(FContext, LTemplate)); -end; - -function TTemplateRegistry.ProcessTemplate(const ATemplateName: string; const AData: T): string; -var - LTemplate: ITemplate; -begin - LTemplate := GetTemplate(ATemplateName); - exit(Template.Eval(FContext, LTemplate, AData)); -end; - -end. diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr index 00e90b9..387c774 100644 --- a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr @@ -1,6 +1,5 @@ program WebBrokerStandalone8080; {$APPTYPE CONSOLE} -{$R 'template.res' 'template.rc'} uses System.SysUtils, @@ -12,11 +11,9 @@ uses Web.WebBroker, WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule}, ServerConst1 in 'ServerConst1.pas', - TemplateRegistry in 'TemplateRegistry.pas', DynForm in 'DynForm.pas'; {$R *.res} -{$R template.res} function BindPort(APort: Integer): Boolean; var diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj index 5564b3e..94f336b 100644 --- a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dproj @@ -157,22 +157,22 @@ MainSource - - template.res -
    WebModule1
    dfm TWebModule
    - + +
    template.res
    +
    - - + + + Base @@ -227,13 +227,19 @@ true
    + + + .\ + true + + .\ true - + .\ true @@ -245,7 +251,7 @@ true - + .\ true diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.rc b/demo/WebBrokerStandalone/WebBrokerStandalone8080.rc new file mode 100644 index 0000000..67cf067 --- /dev/null +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.rc @@ -0,0 +1,6 @@ +index_tpl RCDATA "templates\\index.tpl" +dynform_tpl RCDATA "templates\\dynform.tpl" +helper_tpl RCDATA "templates\\helper.tpl" +template_tpl RCDATA "templates\\template.tpl" +submitted_tpl RCDATA "templates\\submitted.tpl" +error404_tpl RCDATA "templates\\error404.tpl" diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas index 39b1199..65c88f0 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.pas +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -24,7 +24,7 @@ implementation uses DynForm, - TemplateRegistry; + Sempare.Template; {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} diff --git a/demo/WebBrokerStandalone/template.rc b/demo/WebBrokerStandalone/template.rc deleted file mode 100644 index 5429538..0000000 --- a/demo/WebBrokerStandalone/template.rc +++ /dev/null @@ -1,6 +0,0 @@ -index RCDATA "templates\\index.tpl" -dynform RCDATA "templates\\dynform.tpl" -helper RCDATA "templates\\helper_template.tpl" -template RCDATA "templates\\main_template.tpl" -submitted RCDATA "templates\\submitted.tpl" -error404 RCDATA "templates\\404.tpl" diff --git a/demo/WebBrokerStandalone/templates/404.tpl b/demo/WebBrokerStandalone/templates/error404.tpl similarity index 100% rename from demo/WebBrokerStandalone/templates/404.tpl rename to demo/WebBrokerStandalone/templates/error404.tpl diff --git a/demo/WebBrokerStandalone/templates/helper_template.tpl b/demo/WebBrokerStandalone/templates/helper.tpl similarity index 100% rename from demo/WebBrokerStandalone/templates/helper_template.tpl rename to demo/WebBrokerStandalone/templates/helper.tpl diff --git a/demo/WebBrokerStandalone/templates/index.tpl b/demo/WebBrokerStandalone/templates/index.tpl index 67ca8ea..f858f1c 100644 --- a/demo/WebBrokerStandalone/templates/index.tpl +++ b/demo/WebBrokerStandalone/templates/index.tpl @@ -1,6 +1,6 @@ <% extends ( 'template' ) %> <%- block 'body' *%> - +

    Welcome to the Web Broker Demo

    Here are some numbers from 1 to 10:

    diff --git a/demo/WebBrokerStandalone/templates/main_template.tpl b/demo/WebBrokerStandalone/templates/template.tpl similarity index 100% rename from demo/WebBrokerStandalone/templates/main_template.tpl rename to demo/WebBrokerStandalone/templates/template.tpl diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas new file mode 100644 index 0000000..cff150d --- /dev/null +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -0,0 +1,497 @@ +(*%************************************************************************************************* + * ___ * + * / __| ___ _ __ _ __ __ _ _ _ ___ * + * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * + * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * + * |_| * + **************************************************************************************************** + * * + * Sempare Template Engine * + * * + * * + * https://github.com/sempare/sempare-delphi-template-engine * + **************************************************************************************************** + * * + * Copyright (c) 2019-2023 Sempare Limited * + * * + * Contact: info@sempare.ltd * + * * + * Licensed under the GPL Version 3.0 or the Sempare Commercial License * + * You may not use this file except in compliance with one of these Licenses. * + * You may obtain a copy of the Licenses at * + * * + * https://www.gnu.org/licenses/gpl-3.0.en.html * + * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the Licenses is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + *************************************************************************************************%*) +unit Sempare.Template.TemplateRegistry; + +interface + +uses + System.SysUtils, + System.Classes, + System.Types, + System.Generics.Collections, + System.SyncObjs, + Sempare.Template.Context, + Sempare.Template.AST; + +// NOTE: refresh is done simply by using a thread periodically. a more optimal approach could be to use +// file system events, but for development, this should be ok. + +type + ETemplateRefreshTooFrequent = class(Exception) + public + constructor Create; + end; + + ETemplateNotResolved = class(Exception) + public + constructor Create(const ATemplateName: string); + end; + + IRefreshableTemplate = interface(ITemplate) + ['{AC4008EA-336F-4DCB-B6E6-E11034DF5ACF}'] + procedure Refresh; + end; + + TAbstractProxyTemplate = class abstract(TInterfacedObject, ITemplate) + strict protected + FTemplate: ITemplate; + public + constructor Create(const ATemplate: ITemplate); + function GetItem(const AOffset: integer): IStmt; + function GetCount: integer; + function GetLastItem: IStmt; + procedure Optimise; + procedure Accept(const AVisitor: ITemplateVisitor); + function GetFilename: string; + procedure SetFilename(const AFilename: string); + function GetLine: integer; + procedure SetLine(const Aline: integer); + function GetPos: integer; + procedure SetPos(const Apos: integer); + end; + + TResourceTemplate = class(TAbstractProxyTemplate) + public + constructor Create(const AContext: ITemplateContext; const AName: string); + end; + + TFileTemplate = class(TAbstractProxyTemplate, IRefreshableTemplate) + strict private + FContext: ITemplateContext; + FModifiedAt: TDateTime; + procedure Load(const AFilename: string; const ATime: TDateTime); + public + constructor Create(const AContext: ITemplateContext; const AFilename: string); + procedure Refresh; + end; + + TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadFileElseResource); + TTemplateResourceNameResolver = reference to function(const AName: string): string; + TTemplateFileNameResolver = reference to function(const AName: string): string; + + TTemplateRegistry = class + strict private + class var FTemplateRegistry: TTemplateRegistry; + class constructor Create; + class destructor Destroy; + strict private + FLock: TCriticalSection; + FTemplates: TDictionary; + FContext: ITemplateContext; + FLoadStrategy: TTemplateLoadStrategy; + FResourceNameResolver: TTemplateResourceNameResolver; + FFileNameResolver: TTemplateFileNameResolver; + FTemplateRootFolder: string; + FTemplateFileExt: string; + FRefreshIntervalS: integer; + FShutdown: TEvent; + FThreadDone: TEvent; + FAutomaticRefresh: boolean; + + procedure Refresh; + + procedure SetRefreshIntervalS(const Value: integer); + procedure SetAutomaticRefresh(const Value: boolean); + public + constructor Create(); + destructor Destroy; override; + function GetTemplate(const ATemplateName: string): ITemplate; + function ProcessTemplate(const ATemplateName: string; const AData: T): string; overload; + function ProcessTemplate(const ATemplateName: string): string; overload; + class property Instance: TTemplateRegistry read FTemplateRegistry; + + property Context: ITemplateContext read FContext; + property ResourceNameResolver: TTemplateResourceNameResolver read FResourceNameResolver write FResourceNameResolver; + property FileResolver: TTemplateFileNameResolver read FFileNameResolver write FFileNameResolver; + property TemplateRootFolder: string read FTemplateRootFolder write FTemplateRootFolder; + property TemplateFileExt: string read FTemplateFileExt write FTemplateFileExt; + property LoadStrategy: TTemplateLoadStrategy read FLoadStrategy write FLoadStrategy; + property RefreshIntervalS: integer read FRefreshIntervalS write SetRefreshIntervalS; + property AutomaticRefresh: boolean read FAutomaticRefresh write SetAutomaticRefresh; + end; + +implementation + +uses + Sempare.Template.ResourceStrings, + Sempare.Template, + System.IOUtils, + System.DateUtils; + +{ TTemplateRegistry } + +constructor TTemplateRegistry.Create; +var + LPath: string; +begin + FShutdown := TEvent.Create; + FThreadDone := TEvent.Create; + +{$IFDEF DEBUG} + FLoadStrategy := tlsLoadFileElseResource; + FRefreshIntervalS := 5; + AutomaticRefresh := true; +{$ELSE} + FRefreshIntervalS := 10; + FLoadStrategy := tlsLoadResource; + AutomaticRefresh := false; +{$ENDIF} + FLock := TCriticalSection.Create; + FTemplates := TDictionary.Create; + FContext := Template.Context([eoEmbedException]); + + FTemplateFileExt := '.tpl'; + + FResourceNameResolver := function(const AName: string): string + begin + exit(AName.ToLower + TTemplateRegistry.Instance.TemplateFileExt.Replace('.', '_')); + end; + + FFileNameResolver := function(const AName: string): string + begin + exit(TPath.Combine(TTemplateRegistry.Instance.TemplateRootFolder, AName) + TTemplateRegistry.Instance.TemplateFileExt); + end; + + FTemplateRootFolder := TPath.Combine(TPath.GetDirectoryName(paramstr(0)), 'templates'); +{$IFDEF DEBUG} + if not TDirectory.Exists(FTemplateRootFolder) then + begin + LPath := TPath.GetFullPath(TPath.Combine(TPath.Combine(TPath.Combine(TPath.GetDirectoryName(paramstr(0)), '..'), '..'), 'templates')); + if TDirectory.Exists(LPath) then + begin + FTemplateRootFolder := LPath; + end; + end; +{$ENDIF} + FContext.Variable['CopyrightYear'] := YearOf(Now); + + FContext.TemplateResolver := function(const AContext: ITemplateContext; const AName: string): ITemplate + begin + exit(GetTemplate(AName)); + end; +end; + +class constructor TTemplateRegistry.Create; +begin + FTemplateRegistry := TTemplateRegistry.Create; +end; + +destructor TTemplateRegistry.Destroy; +begin + FShutdown.SetEvent; + FThreadDone.WaitFor(); + FThreadDone.Free; + FShutdown.Free; + FLock.Free; + FTemplates.Free; + inherited; +end; + +class destructor TTemplateRegistry.Destroy; +begin + FTemplateRegistry.Free; +end; + +function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; + + function LoadFromRegistry(out ATemplate: ITemplate): boolean; + var + LName: string; + begin + LName := FResourceNameResolver(ATemplateName); + try + ATemplate := TResourceTemplate.Create(FContext, LName); + exit(true); + except + begin + ATemplate := nil; + exit(false); + end; + end; + end; + + function LoadFromFile(out ATemplate: ITemplate): boolean; + var + LName: string; + begin + LName := FFileNameResolver(ATemplateName); + if TFile.Exists(LName) then + begin + try + ATemplate := TFileTemplate.Create(FContext, LName); + exit(true); + except + begin + ATemplate := nil; + exit(false); + end; + end; + end + else + begin + ATemplate := nil; + exit(false); + end; + end; + +begin + FLock.Acquire; + try + if FTemplates.TryGetValue(ATemplateName, result) then + exit; + finally + FLock.Release; + end; + + case TTemplateRegistry.Instance.LoadStrategy of + tlsLoadResource: + if not LoadFromRegistry(result) then + begin + raise ETemplateNotResolved.Create(ATemplateName); + end; + tlsLoadFile: + if not LoadFromFile(result) then + begin + raise ETemplateNotResolved.Create(ATemplateName); + end; + tlsLoadFileElseResource: + if not LoadFromFile(result) and not LoadFromRegistry(result) then + begin + raise ETemplateNotResolved.Create(ATemplateName); + end; + end; + + FLock.Acquire; + try + FTemplates.Add(ATemplateName, result); + finally + FLock.Release; + end; +end; + +function TTemplateRegistry.ProcessTemplate(const ATemplateName: string): string; +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + exit(Template.Eval(FContext, LTemplate)); +end; + +function TTemplateRegistry.ProcessTemplate(const ATemplateName: string; const AData: T): string; +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + exit(Template.Eval(FContext, LTemplate, AData)); +end; + +procedure TTemplateRegistry.Refresh; +var + LRefreshable: IRefreshableTemplate; + LTemplate: ITemplate; +begin + FLock.Acquire; + try + for LTemplate in FTemplates.Values do + begin + if supports(LTemplate, IRefreshableTemplate, LRefreshable) then + begin + LRefreshable.Refresh; + end; + end; + finally + FLock.Release; + end; +end; + +procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); +var + LThread: TThread; +begin + if FAutomaticRefresh = Value then + exit; + + FAutomaticRefresh := Value; + if Value then + begin + LThread := TThread.CreateAnonymousThread( + procedure + begin + while true do + begin + case FShutdown.WaitFor(TTemplateRegistry.Instance.RefreshIntervalS * 1000) of + wrSignaled: + break; + wrTimeout: + ; + end; + Refresh; + end; + FThreadDone.SetEvent; + end); + + TThread.NameThreadForDebugging('Sempare.TTemplateRegistry.RefreshThread', LThread.ThreadID); + LThread.Start; + end + else + begin + FShutdown.SetEvent; + FThreadDone.WaitFor(); + end; +end; + +procedure TTemplateRegistry.SetRefreshIntervalS(const Value: integer); +begin + if Value < 5 then + raise ETemplateRefreshTooFrequent.Create(); + FRefreshIntervalS := Value; +end; + +{ TAbstractProxyTemplate } + +procedure TAbstractProxyTemplate.Accept(const AVisitor: ITemplateVisitor); +begin + FTemplate.Accept(AVisitor); +end; + +constructor TAbstractProxyTemplate.Create(const ATemplate: ITemplate); +begin + FTemplate := ATemplate; +end; + +function TAbstractProxyTemplate.GetCount: integer; +begin + exit(FTemplate.Count); +end; + +function TAbstractProxyTemplate.GetFilename: string; +begin + exit(FTemplate.FileName); +end; + +function TAbstractProxyTemplate.GetItem(const AOffset: integer): IStmt; +begin + exit(FTemplate.GetItem(AOffset)); +end; + +function TAbstractProxyTemplate.GetLastItem: IStmt; +begin + exit(FTemplate.GetLastItem); +end; + +function TAbstractProxyTemplate.GetLine: integer; +begin + exit(FTemplate.line); +end; + +function TAbstractProxyTemplate.GetPos: integer; +begin + exit(FTemplate.pos); +end; + +procedure TAbstractProxyTemplate.Optimise; +begin + FTemplate.Optimise; +end; + +procedure TAbstractProxyTemplate.SetFilename(const AFilename: string); +begin + FTemplate.FileName := AFilename; +end; + +procedure TAbstractProxyTemplate.SetLine(const Aline: integer); +begin + FTemplate.line := Aline; +end; + +procedure TAbstractProxyTemplate.SetPos(const Apos: integer); +begin + FTemplate.pos := Apos; +end; + +{ TResourceTemplate } + +constructor TResourceTemplate.Create(const AContext: ITemplateContext; const AName: string); +var + LStream: TStream; + LTemplate: ITemplate; +begin + LStream := TResourceStream.Create(HInstance, AName, RT_RCDATA); + LTemplate := Template.Parse(AContext, LStream); + inherited Create(LTemplate); + LTemplate.FileName := '[Resource(' + AName + ')]'; +end; + +{ TFileTemplate } + +constructor TFileTemplate.Create(const AContext: ITemplateContext; const AFilename: string); +begin + FContext := AContext; + inherited Create(nil); + Load(AFilename, TFile.GetLastWriteTime(AFilename)); +end; + +procedure TFileTemplate.Load(const AFilename: string; const ATime: TDateTime); +var + LStream: TStream; + LTemplate: ITemplate; +begin + if FModifiedAt = ATime then + exit; + LStream := TBufferedFileStream.Create(AFilename, fmOpenRead); + LTemplate := Template.Parse(FContext, LStream); + inherited Create(LTemplate); + LTemplate.FileName := AFilename; + FModifiedAt := ATime; +end; + +procedure TFileTemplate.Refresh; +begin + Load(FTemplate.FileName, TFile.GetLastWriteTime(FTemplate.FileName)); +end; + +{ ETemplateNotResolved } + +constructor ETemplateNotResolved.Create(const ATemplateName: string); +begin + inherited CreateResFmt(@STemplateNotFound, [ATemplateName]); +end; + +{ ETemplateRefreshTooFrequent } + +constructor ETemplateRefreshTooFrequent.Create; +begin + inherited CreateRes(@SRefreshTooFrequent); +end; + +end. From 24c5af65002c1402173b560bdb016c04e440f428 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 14:45:56 +0100 Subject: [PATCH 055/138] Template.PrettyPrintOutput is called when option eoPrettyPrint is set. It used to call writeln(). This opens up output to UI. --- src/Sempare.Template.Context.pas | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index dfcde5a..9923586 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -82,6 +82,7 @@ interface TTemplateEvaluationOptions = set of TTemplateEvaluationOption; + TPrettyPrintOutput = reference to procedure(const APrettyPrint: string); TTemplateResolver = reference to function(const AContext: ITemplateContext; const AName: string): ITemplate; ITemplateEvaluationContext = interface @@ -153,6 +154,9 @@ interface function GetDebugErrorFormat: string; procedure SetDebugErrorFormat(const AFormat: string); + procedure SetPrettyPrintOutput(const APrettyPrintOutput: TPrettyPrintOutput); + function GetPrettyPrintOutput: TPrettyPrintOutput; + property Functions: ITemplateFunctions read GetFunctions write SetFunctions; property NewLine: string read GetNewLine write SetNewLine; property TemplateResolver: TTemplateResolver read GetTemplateResolver write SetTemplateResolver; @@ -174,6 +178,7 @@ interface property FormatSettings: TFormatSettings read GetFormatSettings; property DebugErrorFormat: string read GetDebugErrorFormat write SetDebugErrorFormat; property StreamWriterProvider: TStreamWriterProvider read GetStreamWriterProvider write SetStreamWriterProvider; + property PrettyPrintOutput: TPrettyPrintOutput read GetPrettyPrintOutput write SetPrettyPrintOutput; end; ITemplateContextForScope = interface @@ -196,7 +201,7 @@ function CreateTemplateContext(const AOptions: TTemplateEvaluationOptions = []): GDefaultEncoding: TEncoding; GUTF8WithoutPreambleEncoding: TUTF8WithoutPreambleEncoding; GStreamWriterProvider: TStreamWriterProvider; - + GPrettyPrintOutput: TPrettyPrintOutput; GDefaultOpenStripWSTag: string = '<|'; GDefaultCloseWSTag: string = '|>'; @@ -251,6 +256,7 @@ TTemplateContext = class(TInterfacedObject, ITemplateContext, ITemplateContext FValueSeparator: char; FFormatSettings: TFormatSettings; FDebugFormat: string; + FPrettyPrintOutput: TPrettyPrintOutput; public constructor Create(const AOptions: TTemplateEvaluationOptions); destructor Destroy; override; @@ -259,6 +265,9 @@ TTemplateContext = class(TInterfacedObject, ITemplateContext, ITemplateContext procedure AddBlock(const AName: string; const ABlock: IBlockStmt); procedure RemoveBlock(const AName: string); + procedure SetPrettyPrintOutput(const APrettyPrintOutput: TPrettyPrintOutput); + function GetPrettyPrintOutput: TPrettyPrintOutput; + procedure StartEvaluation; procedure EndEvaluation; @@ -359,6 +368,7 @@ constructor TTemplateContext.Create(const AOptions: TTemplateEvaluationOptions); begin FOptions := AOptions; FMaxRuntimeMs := GDefaultRuntimeMS; + FPrettyPrintOutput := GPrettyPrintOutput; SetEncoding(GDefaultEncoding); FStartToken := GDefaultOpenTag; FEndToken := GDefaultCloseTag; @@ -456,6 +466,11 @@ function TTemplateContext.GetOptions: TTemplateEvaluationOptions; exit(FOptions); end; +function TTemplateContext.GetPrettyPrintOutput: TPrettyPrintOutput; +begin + result := FPrettyPrintOutput; +end; + function TTemplateContext.GetVariable(const AName: string): TValue; begin FLock.Enter; @@ -565,6 +580,11 @@ procedure TTemplateContext.SetOptions(const AOptions: TTemplateEvaluationOptions FOptions := AOptions; end; +procedure TTemplateContext.SetPrettyPrintOutput(const APrettyPrintOutput: TPrettyPrintOutput); +begin + FPrettyPrintOutput := APrettyPrintOutput; +end; + procedure TTemplateContext.SetValueSeparator(const ASeparator: char); begin {$WARN WIDECHAR_REDUCED OFF} @@ -735,6 +755,10 @@ initialization exit(TNewLineStreamWriter.Create(AStream, AContext.Encoding, AContext.NewLine, AContext.Options)); end; +GPrettyPrintOutput := procedure(const APrettyPrint: string) + begin + end; + finalization GUTF8WithoutPreambleEncoding.Free; From 72d1f1cbfc150d14db3769f7bfd74b623692f268 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 14:47:29 +0100 Subject: [PATCH 056/138] Sempare.Template.TemplateRegistry accessible from Sempare.Template --- src/Sempare.Template.Parser.pas | 3 ++- src/Sempare.Template.ResourceStrings.pas | 1 + src/Sempare.Template.pas | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index a09c95c..88c1258 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -56,6 +56,7 @@ implementation System.Rtti, System.Generics.Collections, Sempare.Template, + Sempare.Template.PrettyPrint, Sempare.Template.BlockResolver, Sempare.Template.ResourceStrings, Sempare.Template.Common, @@ -1907,7 +1908,7 @@ function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boo Match(vsEOF); result.Optimise; if eoPrettyPrint in FContext.Options then - writeln(Template.PrettyPrint(result)); + FContext.PrettyPrintOutput(Template.PrettyPrint(result)); end; function TTemplateParser.PopContainer: ITemplate; diff --git a/src/Sempare.Template.ResourceStrings.pas b/src/Sempare.Template.ResourceStrings.pas index b4b2b9a..1757eec 100644 --- a/src/Sempare.Template.ResourceStrings.pas +++ b/src/Sempare.Template.ResourceStrings.pas @@ -74,6 +74,7 @@ interface SDecimalSeparatorMustBeACommaOrFullStop = 'Decimal separator must be a comma or a full stop'; STooManyParameters = 'Too many parameters'; SInvalidCharacterDetected = 'Invalid character detected'; + SRefreshTooFrequent = 'Template refresh too frequent'; implementation diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index 318cb1c..10cd7ef 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -42,6 +42,7 @@ interface Sempare.Template.Common, Sempare.Template.Context, Sempare.Template.AST, + Sempare.Template.TemplateRegistry, Sempare.Template.Parser; const @@ -58,6 +59,9 @@ interface eoRaiseErrorWhenVariableNotFound = TTemplateEvaluationOption.eoRaiseErrorWhenVariableNotFound; eoReplaceNewline = TTemplateEvaluationOption.eoReplaceNewline; eoStripEmptyLines = TTemplateEvaluationOption.eoStripEmptyLines; + tlsLoadResource = Sempare.Template.TemplateRegistry.tlsLoadResource; + tlsLoadFile = Sempare.Template.TemplateRegistry.tlsLoadFile; + tlsLoadFileElseResource = Sempare.Template.TemplateRegistry.tlsLoadFileElseResource; type TTemplateEvaluationOptions = Sempare.Template.Context.TTemplateEvaluationOptions; @@ -70,6 +74,8 @@ interface TTemplateEncodeFunction = Sempare.Template.Common.TTemplateEncodeFunction; ITemplateVariables = Sempare.Template.Common.ITemplateVariables; TUTF8WithoutPreambleEncoding = Sempare.Template.Context.TUTF8WithoutPreambleEncoding; + TTemplateRegistry = Sempare.Template.TemplateRegistry.TTemplateRegistry; + TTemplateLoadStrategy = Sempare.Template.TemplateRegistry.TTemplateLoadStrategy; Template = class {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} From 3b37af891cb4b5c0337f3e3c07ba745944ffc611 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 14:50:39 +0100 Subject: [PATCH 057/138] add Sempare.Template.TemplateRegistry to project --- Sempare.Template.Pkg.dpk | 6 ++++-- Sempare.Template.Pkg.dproj | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index ccf1e5e..4e312aa 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -61,7 +61,8 @@ package Sempare.Template.Pkg; requires rtl, - dbrtl; + dbrtl, + vcl; contains Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', @@ -80,6 +81,7 @@ contains Sempare.Template in 'src\Sempare.Template.pas', Sempare.Template.JSON in 'src\Sempare.Template.JSON.pas', Sempare.Template.ResourceStrings in 'src\Sempare.Template.ResourceStrings.pas', - Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas'; + Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas', + Sempare.Template.TemplateRegistry in 'src\Sempare.Template.TemplateRegistry.pas'; end. diff --git a/Sempare.Template.Pkg.dproj b/Sempare.Template.Pkg.dproj index b12d3f8..df5ae46 100644 --- a/Sempare.Template.Pkg.dproj +++ b/Sempare.Template.Pkg.dproj @@ -107,6 +107,7 @@ + @@ -220,7 +221,6 @@ - @@ -228,6 +228,8 @@ False False False + False + False False False True From e350bd2f158c988e1105eee85473a435dd2ad305 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 14:51:46 +0100 Subject: [PATCH 058/138] Update --- Sempare.Template.Pkg.dpk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index 4e312aa..af21610 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -61,8 +61,7 @@ package Sempare.Template.Pkg; requires rtl, - dbrtl, - vcl; + dbrtl; contains Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', From 74b1fcef01dbd05ac17f6169fd555f49617cfed0 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 21:46:56 +0100 Subject: [PATCH 059/138] Initial Sempare.Template.RCGenerator.dpr --- Sempare.Template.RCGenerator.dpr | 157 +++ Sempare.Template.RCGenerator.dproj | 1093 ++++++++++++++++++++ Sempare.Template.RCGenerator.rc | 1 + templates/Sempare.Template.RCGenerator.tpl | 3 + 4 files changed, 1254 insertions(+) create mode 100644 Sempare.Template.RCGenerator.dpr create mode 100644 Sempare.Template.RCGenerator.dproj create mode 100644 Sempare.Template.RCGenerator.rc create mode 100644 templates/Sempare.Template.RCGenerator.tpl diff --git a/Sempare.Template.RCGenerator.dpr b/Sempare.Template.RCGenerator.dpr new file mode 100644 index 0000000..96af7f7 --- /dev/null +++ b/Sempare.Template.RCGenerator.dpr @@ -0,0 +1,157 @@ +program Sempare.Template.RCGenerator; + +{$APPTYPE CONSOLE} +{$R *.res} + +uses + Sempare.Template, + System.Generics.Collections, + System.Classes, + System.IOUtils, + System.SysUtils; + +type + THelperClass = class + class function RCName(const AName: string): string; static; + class function EscapeBackslash(const AName: string): string; static; + end; + +procedure ScanDirectory(const ADirectory: string; const AExt: TList; const AFiles: TList); +begin + TDirectory.GetFiles(ADirectory, + function(const APath: string; const SearchRec: TSearchRec): Boolean + var + LExt: string; + begin + for LExt in AExt do + begin + if string(SearchRec.Name).ToLower.EndsWith(LExt.ToLower) then + begin + AFiles.Add(TPath.Combine(APath, SearchRec.Name)); + break; + end; + end; + exit(false); + end); + + TDirectory.GetDirectories(ADirectory, + function(const APath: string; const SearchRec: TSearchRec): Boolean + begin + ScanDirectory(TPath.Combine(APath, SearchRec.Name), AExt, AFiles); + exit(false); + end); +end; + +procedure NormaliseInput(var ATempalteRelPath: string; const AFiles: TList); +var + i: integer; +begin + if not ATempalteRelPath.EndsWith(TPath.DirectorySeparatorChar) then + ATempalteRelPath := ATempalteRelPath + TPath.DirectorySeparatorChar; + for i := 0 to AFiles.Count - 1 do + begin + AFiles[i] := ExtractRelativePath(ATempalteRelPath, AFiles[i]); + end; +end; + +procedure WriteRC(const AFilename: string; const AFiles: TList); +type + TData = record + Files: TList; + end; +var + LStream: TBufferedFileStream; + LData: TData; +begin + LData.Files := AFiles; + LStream := TBufferedFileStream.Create(AFilename, fmCreate); + try + TTemplateRegistry.Instance.Eval(LStream, 'sempare_template_rcgenerator_tpl', LData) + finally + LStream.Free; + end; +end; + +const + DEFAULT_EXTS: TArray // + = ['.ico', '.png', '.jpg', '.jpeg', '.webp', '.tpl', '.bmp', '.gif', '.wbmp']; + +procedure main; +var + LRCFilename: string; + LTemplatePath: string; + LTempalteRelPath: string; + LExt: TList; + LFiles: TList; + i: integer; +begin + if ParamCount < 2 then + begin + writeln(TPath.GetFilename(ParamStr(0)) + ' [+]'); + writeln; + writeln(' is the path to the filename that will be generated'); + writeln(' is the path to the directory listing all the templates'); + writeln('+ is one one or more extensions to be included. By default: ' + string.Join(', ', DEFAULT_EXTS)); + writeln(''); + exit; + end; + LRCFilename := TPath.GetFullPath(ParamStr(1)); + LTemplatePath := TPath.GetFullPath(ParamStr(2)); + LTempalteRelPath := TPath.GetDirectoryName(LRCFilename); + LFiles := nil; + LExt := nil; + + TTemplateRegistry.Instance.LoadStrategy := tlsLoadResource; + TTemplateRegistry.Instance.Context.Functions.AddFunctions(THelperClass); + + try + LFiles := TList.Create; + LExt := TList.Create; + + LExt.AddRange(DEFAULT_EXTS); + + if ParamCount > 2 then + begin + LExt.Clear; + for i := 3 to ParamCount do + begin + LExt.Add(ParamStr(i)); + end; + end; + ScanDirectory(LTemplatePath, LExt, LFiles); + NormaliseInput(LTempalteRelPath, LFiles); + WriteRC(LRCFilename, LFiles); + finally + LExt.Free; + LFiles.Free; + end; +end; + +{ THelperClass } + +class function THelperClass.EscapeBackslash(const AName: string): string; +begin + exit(AName.Replace(TPath.DirectorySeparatorChar, TPath.DirectorySeparatorChar + TPath.DirectorySeparatorChar, [rfReplaceAll])); +end; + +class function THelperClass.RCName(const AName: string): string; +var + LParts: TArray; +begin + result := AName; + LParts := result.Split([TPath.DirectorySeparatorChar]); + delete(LParts, 0, 1); + result := string.Join(TPath.DirectorySeparatorChar, LParts); + result := result.ToLower; + result := result.Replace('.', '_', [rfReplaceAll]); +end; + +begin + try + main; + except + on E: Exception do + writeln(E.ClassName, ': ', E.Message); + end; + +end. diff --git a/Sempare.Template.RCGenerator.dproj b/Sempare.Template.RCGenerator.dproj new file mode 100644 index 0000000..9f45049 --- /dev/null +++ b/Sempare.Template.RCGenerator.dproj @@ -0,0 +1,1093 @@ + + + {6855F9A8-CBBA-4924-BA0B-9C45E19E6ADE} + 19.5 + None + True + Debug + Win32 + 1 + Console + Sempare.Template.RCGenerator.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + Sempare_Template_RCGenerator + src;$(DCC_UnitSearchPath) + 2057 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + FlexCel_Report;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;IndyProtocols;FMX_FlexCel_Components;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;FlexCel_Pdf;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FlexCel_XlsAdapter;CustomIPTransport;DBXInterBaseDriver;IndySystem;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + activity-1.1.0.dex.jar;annotation-1.2.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;biometric-1.1.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.1.0.dex.jar;core-runtime-2.1.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.2.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.2.0.dex.jar;lifecycle-runtime-2.2.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.2.0.dex.jar;lifecycle-viewmodel-savedstate-2.2.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;savedstate-1.0.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + FlexCel_Report;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;IndyProtocols;FMX_FlexCel_Components;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;FlexCel_Pdf;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FlexCel_XlsAdapter;CustomIPTransport;DBXInterBaseDriver;IndySystem;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + activity-1.1.0.dex.jar;annotation-1.2.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;biometric-1.1.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.1.0.dex.jar;core-runtime-2.1.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.2.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.2.0.dex.jar;lifecycle-runtime-2.2.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.2.0.dex.jar;lifecycle-viewmodel-savedstate-2.2.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;savedstate-1.0.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + FlexCel_Report;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;IndyProtocols;FMX_FlexCel_Components;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;FlexCel_Pdf;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FlexCel_XlsAdapter;CustomIPTransport;DBXInterBaseDriver;IndySystem;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;bindcomp;FireDACCommon;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;dsnap;CloudService;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + DataSnapServer;FlexCel_Report;fmx;emshosting;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;FMXTMSFNCWebSocketPkgDXE14;emsedge;FlexCel_Pdf;SKIA_FlexCel_Core;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;FMXTMSFNCWXPackPkgDXE14;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;FlexCel_XlsAdapter;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;FlexCel_Core;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + DataSnapServer;FlexCel_Report;fmx;DbxCommonDriver;bindengine;IndyIPCommon;FireDACCommonODBC;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;IndyProtocols;FMX_FlexCel_Components;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;FlexCel_Pdf;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;fmxFireDAC;dbexpress;DBXMySQLDriver;inet;DataSnapCommon;fmxase;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;FlexCel_XlsAdapter;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;DBXInformixDriver;fmxobj;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + true + + + DataSnapServer;fmx;DbxCommonDriver;bindengine;IndyIPCommon;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;FmxTeeUI;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;fmxFireDAC;dbexpress;DBXMySQLDriver;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;RESTComponents;DBXSqliteDriver;IndyIPServer;dsnapxml;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;DBXInformixDriver;fmxobj;FMXTee;DataSnapNativeClient;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + true + + + vclwinx;DataSnapServer;Sempare.IDE.DebugConfigurations.Pkg;FlexCel_Report;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;appanalytics;IndyProtocols;vclx;FMX_FlexCel_Components;IndyIPClient;dbxcds;vcledge;VCLTMSFNCWXPackPkgDXE14;bindcompvclwinx;FmxTeeUI;VCLTMSFNCUIPackPkgDXE14;FMXTMSFNCWebSocketPkgDXE14;emsedge;bindcompfmx;FlexCel_Pdf;DBXFirebirdDriver;VCLTMSFNCWebSocketPkgDXE14;SKIA_FlexCel_Core;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FMXTMSFNCWXPackPkgDXE14;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;TMSWEBCorePkgLibDXE14;DBXOdbcDriver;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FireDACOracleDriver;TMSWEBCorePkgDXE14;fmxdae;TeeDB;FMXTMSFNCBloxPkgDXE14;FlexCel_XlsAdapter;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;VCLTMSFNCCorePkgDXE14;vcldb;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;vclFireDAC;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;VCLTMSFNCBloxPkgDXE14;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + vclwinx;DataSnapServer;FlexCel_Report;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;FMXTMSFNCMapsPkgDXE14;appanalytics;IndyProtocols;vclx;FMX_FlexCel_Components;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;VCLTMSFNCUIPackPkgDXE14;emsedge;bindcompfmx;FlexCel_Pdf;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;DBXOdbcDriver;FMX_FlexCel_Core;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;FlexCel_XlsAdapter;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;VCLTMSFNCCorePkgDXE14;vcldb;FMXTMSFNCUIPackPkgDXE14;FlexCel_Core;vclFireDAC;FMXTMSFNCCorePkgDXE14;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;FlexCel_Render;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;FMXTee;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + D:\Sempare\vm-mount\sempare-delphi-template-engine-mars-demo\Sempare.TemplateEngineMarsDemo2.rc D:\Sempare\vm-mount\sempare-delphi-template-engine-mars-demo\templates + 1033 + (None) + none + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + +
    Sempare.Template.RCGenerator.res
    +
    + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
    + + Delphi.Personality.12 + Application + + + + Sempare.Template.RCGenerator.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + (untitled) + + + + + + true + + + + + true + + + + + true + + + + + Sempare_Template_RCGenerator.exe + true + + + + + .\ + true + + + + + .\ + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 64 + + + classes + 64 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + False + False + False + False + False + False + False + True + False + + + 12 + + + + +
    diff --git a/Sempare.Template.RCGenerator.rc b/Sempare.Template.RCGenerator.rc new file mode 100644 index 0000000..7e739bc --- /dev/null +++ b/Sempare.Template.RCGenerator.rc @@ -0,0 +1 @@ +sempare_template_rcgenerator_tpl RCDATA "templates\\Sempare.Template.RCGenerator.tpl" diff --git a/templates/Sempare.Template.RCGenerator.tpl b/templates/Sempare.Template.RCGenerator.tpl new file mode 100644 index 0000000..3080cae --- /dev/null +++ b/templates/Sempare.Template.RCGenerator.tpl @@ -0,0 +1,3 @@ +<%- for filename in files *%> +<% RCName(filename) %> RCDATA "<% EscapeBackslash(filename) %>" +<%- end *%> From d946532f53315cff95ff46560113fccd853c59da Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 21:48:17 +0100 Subject: [PATCH 060/138] Refactor TemplateRegistry --- demo/WebBrokerStandalone/WebModuleUnit1.pas | 8 +- src/Sempare.Template.TemplateRegistry.pas | 133 +++++++++++++------- 2 files changed, 89 insertions(+), 52 deletions(-) diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas index 65c88f0..e4f232e 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.pas +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -31,13 +31,13 @@ implementation procedure TWebModule1.WebModule1IndexHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin - Response.Content := TTemplateRegistry.Instance.ProcessTemplate('index'); + Response.Content := TTemplateRegistry.Instance.Eval('index'); Handled := true; end; procedure TWebModule1.WebModule1ErrorHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin - Response.Content := TTemplateRegistry.Instance.ProcessTemplate('error404'); + Response.Content := TTemplateRegistry.Instance.Eval('error404'); Response.StatusCode := 404; Handled := true; end; @@ -55,7 +55,7 @@ procedure TWebModule1.WebModule1FormInputAction(Sender: TObject; Request: TWebRe TField.create('Email', 'email', 'TEmail') // ]; LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; - Response.Content := TTemplateRegistry.Instance.ProcessTemplate('dynform', LTemplateData); + Response.Content := TTemplateRegistry.Instance.Eval('dynform', LTemplateData); Handled := true; end; @@ -72,7 +72,7 @@ procedure TWebModule1.WebModule1FormInputHandlerAction(Sender: TObject; Request: LFormData.firstname := Params.Values['firstname']; LFormData.lastname := Params.Values['lastname']; LFormData.email := Params.Values['email']; - Response.Content := TTemplateRegistry.Instance.ProcessTemplate('submitted', LFormData); + Response.Content := TTemplateRegistry.Instance.Eval('submitted', LFormData); finally Params.Free; end; diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index cff150d..a4b38be 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -116,26 +116,32 @@ TTemplateRegistry = class FRefreshIntervalS: integer; FShutdown: TEvent; FThreadDone: TEvent; + FThread: TThread; FAutomaticRefresh: boolean; procedure Refresh; procedure SetRefreshIntervalS(const Value: integer); procedure SetAutomaticRefresh(const Value: boolean); + procedure SetLoadStrategy(const Value: TTemplateLoadStrategy); + private + procedure SetTemplateFileExt(const Value: string); public constructor Create(); destructor Destroy; override; function GetTemplate(const ATemplateName: string): ITemplate; - function ProcessTemplate(const ATemplateName: string; const AData: T): string; overload; - function ProcessTemplate(const ATemplateName: string): string; overload; + procedure Eval(const AOutputStream: TStream; const ATemplateName: string); overload; + procedure Eval(const AOutputStream: TStream; const ATemplateName: string; const AData: T); overload; + function Eval(const ATemplateName: string; const AData: T): string; overload; + function Eval(const ATemplateName: string): string; overload; class property Instance: TTemplateRegistry read FTemplateRegistry; property Context: ITemplateContext read FContext; property ResourceNameResolver: TTemplateResourceNameResolver read FResourceNameResolver write FResourceNameResolver; property FileResolver: TTemplateFileNameResolver read FFileNameResolver write FFileNameResolver; property TemplateRootFolder: string read FTemplateRootFolder write FTemplateRootFolder; - property TemplateFileExt: string read FTemplateFileExt write FTemplateFileExt; - property LoadStrategy: TTemplateLoadStrategy read FLoadStrategy write FLoadStrategy; + property TemplateFileExt: string read FTemplateFileExt write SetTemplateFileExt; + property LoadStrategy: TTemplateLoadStrategy read FLoadStrategy write SetLoadStrategy; property RefreshIntervalS: integer read FRefreshIntervalS write SetRefreshIntervalS; property AutomaticRefresh: boolean read FAutomaticRefresh write SetAutomaticRefresh; end; @@ -157,29 +163,20 @@ constructor TTemplateRegistry.Create; FShutdown := TEvent.Create; FThreadDone := TEvent.Create; -{$IFDEF DEBUG} - FLoadStrategy := tlsLoadFileElseResource; - FRefreshIntervalS := 5; - AutomaticRefresh := true; -{$ELSE} - FRefreshIntervalS := 10; - FLoadStrategy := tlsLoadResource; - AutomaticRefresh := false; -{$ENDIF} FLock := TCriticalSection.Create; FTemplates := TDictionary.Create; FContext := Template.Context([eoEmbedException]); - FTemplateFileExt := '.tpl'; + TemplateFileExt := '.tpl'; FResourceNameResolver := function(const AName: string): string begin - exit(AName.ToLower + TTemplateRegistry.Instance.TemplateFileExt.Replace('.', '_')); + exit(AName.ToLower.Replace('.', '_', [rfReplaceAll])); end; FFileNameResolver := function(const AName: string): string begin - exit(TPath.Combine(TTemplateRegistry.Instance.TemplateRootFolder, AName) + TTemplateRegistry.Instance.TemplateFileExt); + exit(TPath.Combine(TTemplateRegistry.Instance.TemplateRootFolder, AName)); end; FTemplateRootFolder := TPath.Combine(TPath.GetDirectoryName(paramstr(0)), 'templates'); @@ -199,6 +196,16 @@ constructor TTemplateRegistry.Create; begin exit(GetTemplate(AName)); end; + +{$IFDEF DEBUG} + FLoadStrategy := tlsLoadFileElseResource; + FRefreshIntervalS := 5; + AutomaticRefresh := true; +{$ELSE} + FRefreshIntervalS := 10; + FLoadStrategy := tlsLoadResource; + AutomaticRefresh := false; +{$ENDIF} end; class constructor TTemplateRegistry.Create; @@ -227,40 +234,41 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; function LoadFromRegistry(out ATemplate: ITemplate): boolean; var LName: string; + LExt: string; begin - LName := FResourceNameResolver(ATemplateName); - try - ATemplate := TResourceTemplate.Create(FContext, LName); - exit(true); - except - begin - ATemplate := nil; - exit(false); + ATemplate := nil; + for LExt in [FTemplateFileExt, ''] do + begin + LName := FResourceNameResolver(ATemplateName + LExt); + try + ATemplate := TResourceTemplate.Create(FContext, LName); + exit(true); + except + // do nothing, lets try the next ext end; end; + exit(false); end; function LoadFromFile(out ATemplate: ITemplate): boolean; var LName: string; + LExt: string; begin - LName := FFileNameResolver(ATemplateName); - if TFile.Exists(LName) then + result := false; + ATemplate := nil; + for LExt in [FTemplateFileExt, ''] do begin - try - ATemplate := TFileTemplate.Create(FContext, LName); - exit(true); - except - begin - ATemplate := nil; - exit(false); + LName := FFileNameResolver(ATemplateName + FTemplateFileExt); + if TFile.Exists(LName) then + begin + try + ATemplate := TFileTemplate.Create(FContext, LName); + exit(true); + except + // do nothing, try next extension end; end; - end - else - begin - ATemplate := nil; - exit(false); end; end; @@ -299,7 +307,7 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; end; end; -function TTemplateRegistry.ProcessTemplate(const ATemplateName: string): string; +function TTemplateRegistry.Eval(const ATemplateName: string): string; var LTemplate: ITemplate; begin @@ -307,7 +315,23 @@ function TTemplateRegistry.ProcessTemplate(const ATemplateName: string): string; exit(Template.Eval(FContext, LTemplate)); end; -function TTemplateRegistry.ProcessTemplate(const ATemplateName: string; const AData: T): string; +procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplateName: string); +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + Template.Eval(FContext, LTemplate, AOutputStream); +end; + +procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplateName: string; const AData: T); +var + LTemplate: ITemplate; +begin + LTemplate := GetTemplate(ATemplateName); + Template.Eval(FContext, LTemplate, AData, AOutputStream); +end; + +function TTemplateRegistry.Eval(const ATemplateName: string; const AData: T): string; var LTemplate: ITemplate; begin @@ -335,8 +359,6 @@ procedure TTemplateRegistry.Refresh; end; procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); -var - LThread: TThread; begin if FAutomaticRefresh = Value then exit; @@ -344,7 +366,7 @@ procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); FAutomaticRefresh := Value; if Value then begin - LThread := TThread.CreateAnonymousThread( + FThread := TThread.CreateAnonymousThread( procedure begin while true do @@ -359,17 +381,25 @@ procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); end; FThreadDone.SetEvent; end); - - TThread.NameThreadForDebugging('Sempare.TTemplateRegistry.RefreshThread', LThread.ThreadID); - LThread.Start; +{$IFDEF DEBUG} + TThread.NameThreadForDebugging('TTemplateRegistry.RefreshThread', FThread.ThreadID); +{$ENDIF} + FThread.Start; end - else + else if FThread <> nil then begin FShutdown.SetEvent; FThreadDone.WaitFor(); end; end; +procedure TTemplateRegistry.SetLoadStrategy(const Value: TTemplateLoadStrategy); +begin + FLoadStrategy := Value; + if FLoadStrategy = tlsLoadResource then + AutomaticRefresh := false; +end; + procedure TTemplateRegistry.SetRefreshIntervalS(const Value: integer); begin if Value < 5 then @@ -377,6 +407,13 @@ procedure TTemplateRegistry.SetRefreshIntervalS(const Value: integer); FRefreshIntervalS := Value; end; +procedure TTemplateRegistry.SetTemplateFileExt(const Value: string); +begin + FTemplateFileExt := Value; + if not FTemplateFileExt.StartsWith('.') then + FTemplateFileExt := '.' + FTemplateFileExt; +end; + { TAbstractProxyTemplate } procedure TAbstractProxyTemplate.Accept(const AVisitor: ITemplateVisitor); @@ -468,7 +505,7 @@ procedure TFileTemplate.Load(const AFilename: string; const ATime: TDateTime); begin if FModifiedAt = ATime then exit; - LStream := TBufferedFileStream.Create(AFilename, fmOpenRead); + LStream := TBufferedFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone); LTemplate := Template.Parse(FContext, LStream); inherited Create(LTemplate); LTemplate.FileName := AFilename; From 365723624f6db1d57dac87fffaff27835cf419f5 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 22:15:35 +0100 Subject: [PATCH 061/138] Add block comments for package generation --- Sempare.Template.Pkg.dpk | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index af21610..8ac6900 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -1,4 +1,5 @@ - (*%************************************************************************************************* +{+ block 'copyright' +} +(*%************************************************************************************************* * ___ * * / __| ___ _ __ _ __ __ _ _ _ ___ * * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * @@ -12,7 +13,7 @@ * https://github.com/sempare/sempare-delphi-template-engine * **************************************************************************************************** * * - * Copyright (c) 2019-2021 Sempare Limited * + * Copyright (c) 2019-2021 Sempare Limited * * * * Contact: info@sempare.ltd * * * @@ -30,8 +31,10 @@ * limitations under the License. * * * *************************************************************************************************%*) -package Sempare.Template.Pkg; +{+ end +} +package Sempare.Template.Pkg{+ CompilerVersion +}; +{+ block 'directives' +} {$R *.res} {$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} {$ALIGN 8} @@ -58,12 +61,17 @@ package Sempare.Template.Pkg; {$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} {$IMPLICITBUILD ON} +{+ end +} requires +{+ block 'requires' +} rtl, - dbrtl; + dbrtl, + vcl; +{+ end +} contains +{+ block 'contains' +} Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', Sempare.Template.Visitor in 'src\Sempare.Template.Visitor.pas', Sempare.Template.Util in 'src\Sempare.Template.Util.pas', @@ -82,5 +90,6 @@ contains Sempare.Template.ResourceStrings in 'src\Sempare.Template.ResourceStrings.pas', Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas', Sempare.Template.TemplateRegistry in 'src\Sempare.Template.TemplateRegistry.pas'; +{+ end +} end. From 66a8a58985ce7a70a2e2cf88c44a98d1432be3d1 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 22:17:10 +0100 Subject: [PATCH 062/138] Fix links in readme and provide context around changes in whitespace-removal.md --- README.md | 95 +++++++++++++++++++++++--------------- docs/whitespace-removal.md | 20 +++++++- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 684ebc8..5f42b5d 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,35 @@ License: [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) or [Sempare Li Open Source: https://github.com/sempare/sempare-delphi-template-engine ## Contents -1. [Introduction](#Introduction) -2. [Quickstart](#Quickstart) -3. [Features](#Features) -4. [Objectives](#Objectives) -5. [Requirements](#Requirements) -6. [Installation: GetIt](#GetIt) -7. [Installation: Delphinus](#DelphinusSupport) -8. [Installation: Manual Install](#ManualInstall) -9. [Feedback](#Feedback) -10. [Statements](./docs/statements.md) -11. [Expressions](./docs/expressions.md) -12. [Builtin functions](./docs/builtin-functions.md) -13. [Builtin variables](./docs/builtin-variables.md) -14. [Custom functions](./docs/custom-functions.md) -15. [Configuration](./docs/configuration.md) -16. [Components](./docs/components.md) -17. [Tricks](./docs/tricks.md) -18. [Template Patterns](./docs/template-patterns.md) -19. [Whitespace Removal](./docs/whitespace-removal.md) -20. [Internals](./docs/internals.md) -21. [Restrictions/Limitations/Known Bugs](./docs/restrictions.md) -22. [License](#License) - -## Introduction +- [Introduction](#Introduction) +- [Call To Action](#CallToAction) +- [Quickstart](#Quickstart) +- [Features](#Features) +- [Objectives](#Objectives) +- [Requirements](#Requirements) +- [Installation](#Installation) + - [GetIt](#GetIt) + - [Boss](#Boss) + - [Delphinus](#DelphinusSupport) + - [Manual Install](#ManualInstall) +- [Feedback](#Feedback) +- Template Language + - [Statements](./docs/statements.md) + - [Expressions](./docs/expressions.md) + - [Builtin functions](./docs/builtin-functions.md) + - [Builtin variables](./docs/builtin-variables.md) +- Customisation + - [Custom functions](./docs/custom-functions.md) + - [Configuration](./docs/configuration.md) +- [Components](./docs/components.md) +- [Tricks](./docs/tricks.md) +- [Template Patterns](./docs/template-patterns.md) +- [Whitespace Removal](./docs/whitespace-removal.md) +- [Internals](./docs/internals.md) +- [Restrictions/Limitations/Known Bugs](./docs/restrictions.md) +- [License](#License) + +

    Introduction

    Template engines are used often in technology where text needs to be customised by substituting variables with values from a data source. Examples where this may take place: - web sites using template engines (for server side scripting) @@ -78,21 +83,26 @@ In the example above, you can see that the '<%' start and '%>' end the scripting **NOTE** In examples in this documentation I may use the latest Delphi syntax, e.g. inline variable declarations. This is not backward compatible as they were introduced in Delphi 10.2 and are used to shorten the code/examples being illustrated in the documentation. The codebase will attempt to be as backward compatible as possible. -## Call to action +

    Call to action

    Please 'star' the project on github. ![](./images/sempare-template-engine-start-cta.png) -## Quickstart +

    Quickstart

    + +There are a few ways to get started quickly. + +- Look at the examples on how to do server side scripting using some popular web frameworks: + - [WebBroker](https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Creating_WebBroker_Applications) [Demo](https://github.com/sempare/sempare-delphi-template-engine/tree/main/demo/WebBrokerStandalone) + - [Horse](https://github.com/hashload/horse) [Demo](https://github.com/sempare/sempare-delphi-template-engine-horse-demo) [Try the demo](./demo/VelocityDemo/README.md) if you want to dive in quick and play with the template engine. Quick tutorials on [You Tube](https://www.youtube.com/playlist?list=PLjjz4SuVScHreGKEInvrjPtLPMBU6l130). The playlist has a few videos that are very short (most less than a minute - blink and they are done). You can drag the slider in the videos if you miss something or refer to the rest of the documentation. - -## Features +

    Features

    - statements - if, elif, else statements - for and while statements @@ -117,13 +127,13 @@ The playlist has a few videos that are very short (most less than a minute - bli - allow use of custom encoding (UTF-8 with BOM, UTF-8 without BOM, ASCII, etc) - extensibile RTTI interface to easily dereference classes and interfaces (current customisations for ITemplateVariables, TDictionary, TJsonObject) -## Objectives +

    Objectives

    The Sempare Template Engine is not intended to be a fully featured general purpose programming language such as PHP where the script itself could be a self contained programming language (but it does have most of the features). Sempare Template Engine aims to provide just enough functionality to allow you to easily work with the 'view' aspects of a template. Any enhanced functionality required from the scripting environment should be provided by the custom functions written in Object Pascal. -## Requirements +

    Requirements

    The template engine works with modern versions of [Delphi](https://www.embarcadero.com/products/delphi). @@ -146,19 +156,31 @@ Have a look at Sempare.Template.Compiler.inc. The following defines can be defin - SEMPARE_TEMPLATE_NO_INDY - if Indy is not present. This is used to access an html encoder if TNetEncoding is not available. - SEMPARE_TEMPLATE_CONFIRM_LICENSE - if present, you confirm you understand the conditions. -

    Installation: GetIt

    +

    Installation

    + +

    GetIt

    The Sempare Template Engine for Delphi can be installed via the [Embarcadero GetIt manager](https://getitnow.embarcadero.com/?q=sempare&product=rad-studio) This will add the *src* folder to the search path so you can start working immediately. -

    Installation: Delphinus Support

    +

    Boss

    + +The Sempare Template Engine for Delphi can be installed via the [Boss](https://github.com/hashload/boss/releases) package manager. + +Simply run: +``` +boss install sempare/sempare-delphi-template-engine +``` + + +

    Delphinus

    The Sempare Template Engine for Delphi can be installed via the [Delphinus](https://github.com/Memnarch/Delphinus) package manager. This will add the *src* folder to the search path so you can start working immediately. -

    Installation: Manual Install

    +

    Manual Install

    Start by adding the *src* folder to the Delphi search path. Otherwise, there are some projects you can use: @@ -176,18 +198,17 @@ Open __Sempare.Template.Engine.Group.groupproj__ which will include: The velocity real-time demo. - -## Feedback +

    Feedback

    You can raise issues on [GitHub](https://github.com/sempare/sempare.template) and they will be addressed based on priority. Most features have some basic tests in place. If a bug is been discovered, please include a basic test/scenario replicating the issue if possible as this will ease the investigation process. -# Contributions +

    Contributions

    Review [contibution terms and conditions](./docs/CONTRIBUTION.pdf) to contribute to the project. -# License +

    License

    The Sempare Template Engine is dual-licensed. You may choose to use it under the restrictions of the [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) at no cost to you, or you may license it for use under the [Sempare Limited Commercial License](./docs/commercial.license.md) diff --git a/docs/whitespace-removal.md b/docs/whitespace-removal.md index 739c3c5..8c3f8bb 100644 --- a/docs/whitespace-removal.md +++ b/docs/whitespace-removal.md @@ -4,6 +4,8 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ## Whitespace Removal +v1.7.0 introduces some new concepts is still experimental and will change slightly. The new behaviour that will be provided in an v1.7.1 is described in the 'general notes' below. + Removing whitespace is a tricky problem in templates. In the template engine, attempts have been made to address this in a number of ways. ### Using script tag hints @@ -97,4 +99,20 @@ This is important to appreciate especially when dealing with looping. #### General notes - If you want to remove a script block from the output, use <%- *%> -- Use the template engine demo to play to see how it behaves. \ No newline at end of file +- Use the template engine demo to play to see how it behaves. +- In v1.7.1, the basics described above will remain, however, in the case of script tags that have an end, the stripping action hints will be applied across all lines in the contained block. + +e.g. +``` +<%- if cond *%> +<% 'hello' %> +<% end %> +``` + +The above example illustrates what we will be doing in an upcoming release. Rather than having to apply the hinting at each level, the hinting will +be applied to all lines within the block. + +Thus the above would result in the following if the cond is true: +``` +hello +``` \ No newline at end of file From 4d85a6c8ead191365600417d97f0761595c77f19 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Sun, 2 Apr 2023 22:29:26 +0100 Subject: [PATCH 063/138] TemplateRegistry placeholder docs --- README.md | 1 + demo/WebBrokerStandalone/README.md | 2 +- docs/template-registry.md | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 docs/template-registry.md diff --git a/README.md b/README.md index 5f42b5d..a39f61e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Open Source: https://github.com/sempare/sempare-delphi-template-engine - [Expressions](./docs/expressions.md) - [Builtin functions](./docs/builtin-functions.md) - [Builtin variables](./docs/builtin-variables.md) +- [Template Registry](./docs/template-registry.md) - Customisation - [Custom functions](./docs/custom-functions.md) - [Configuration](./docs/configuration.md) diff --git a/demo/WebBrokerStandalone/README.md b/demo/WebBrokerStandalone/README.md index 4c08ac4..0d01400 100644 --- a/demo/WebBrokerStandalone/README.md +++ b/demo/WebBrokerStandalone/README.md @@ -25,7 +25,7 @@ This is the web module that has been slightly modified for the example. It uses DynForm and TemplateRegistry. You should observe that templates are loaded using: - TTemplateRegistry.Instance.ProcessTemplate(templatename, data); + TTemplateRegistry.Instance.Eval(templatename, data); # Limitations diff --git a/docs/template-registry.md b/docs/template-registry.md new file mode 100644 index 0000000..2266957 --- /dev/null +++ b/docs/template-registry.md @@ -0,0 +1,28 @@ +# ![](../images/sempare-logo-45px.png) Sempare Template Engine + +Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) + +## Template Registry + +The TTemplateRegistry utility class is the easiest way to maintain multiple templates in a project, spanning file and application resources. + +TODO: +- describe default behaviour +- customisation + - naming overrides + - live changes using auto refresh + - loading strategies (file, resource, file else resource) + - context + +## Resource File Creation Tool + +The Sempare.Template.RCGenerator.exe is available that can generate your .rc file for you quickly. + +``` +PS > .\Sempare.Template.RCGenerator.exe +Sempare.Template.RCGenerator.exe [+] + + is the path to the filename that will be generated + is the path to the directory listing all the templates ++ is one one or more extensions to be included. By default: .ico, .png, .jpg, .jpeg, .webp, .tpl, .bmp, .gif, .wbmp +``` \ No newline at end of file From 6e36f9b76bd9d05b2f0ed52671d713d2c9a0cb8a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 14:03:11 +0100 Subject: [PATCH 064/138] Refactor examples --- demo/WebBrokerStandalone/WebModuleUnit1.pas | 37 ++++- .../WebBrokerStandalone/templates/dynform.tpl | 7 +- demo/WebBrokerStandalone/templates/index.tpl | 26 ++- .../templates/template.tpl | 1 + src/Sempare.Template.TemplateRegistry.pas | 157 +++++++++++++----- src/Sempare.Template.pas | 3 +- 6 files changed, 182 insertions(+), 49 deletions(-) diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas index e4f232e..dad9126 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.pas +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -29,9 +29,24 @@ implementation {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} +type + TDemo = record + Name: string; + FrameworkUrl: string; + Url: string; + Current: Boolean; + constructor Create(const AName: String; const AFrameworkUrl, AUrl: string; const ACurrent: Boolean = false); + end; + procedure TWebModule1.WebModule1IndexHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +var + LDemos: TArray; begin - Response.Content := TTemplateRegistry.Instance.Eval('index'); + LDemos := [ // + TDemo.Create('Web Broker', 'https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Creating_WebBroker_Applications', 'https://github.com/sempare/sempare-delphi-template-engine/tree/main/demo/WebBrokerStandalone', true), // + TDemo.Create('Horse', 'https://github.com/HashLoad/horse', 'https://github.com/sempare/sempare-delphi-template-engine-horse-demo') // + ]; + Response.Content := TTemplateRegistry.Instance.Eval('index', LDemos); Handled := true; end; @@ -50,11 +65,11 @@ procedure TWebModule1.WebModule1FormInputAction(Sender: TObject; Request: TWebRe LTemplateData.FormName := 'userinfo'; LTemplateData.FormAction := Request.PathInfo; LTemplateData.Fields := [ // - TField.create('FirstName', 'firstname'), // - TField.create('LastName', 'lastname'), // - TField.create('Email', 'email', 'TEmail') // + TField.Create('FirstName', 'firstname'), // + TField.Create('LastName', 'lastname'), // + TField.Create('Email', 'email', 'TEmail') // ]; - LTemplateData.Buttons := [TButton.create('Submit', 'submit')]; + LTemplateData.Buttons := [TButton.Create('Submit', 'submit')]; Response.Content := TTemplateRegistry.Instance.Eval('dynform', LTemplateData); Handled := true; end; @@ -66,7 +81,7 @@ procedure TWebModule1.WebModule1FormInputHandlerAction(Sender: TObject; Request: Params: TStrings; begin PostData := Request.Content; - Params := TStringList.create; + Params := TStringList.Create; try ExtractStrings(['&'], [], PChar(PostData), Params); LFormData.firstname := Params.Values['firstname']; @@ -79,4 +94,14 @@ procedure TWebModule1.WebModule1FormInputHandlerAction(Sender: TObject; Request: Handled := true; end; +{ TDemo } + +constructor TDemo.Create(const AName, AFrameworkUrl, AUrl: string; const ACurrent: Boolean); +begin + name := AName; + FrameworkUrl := AFrameworkUrl; + Url := AUrl; + Current := ACurrent; +end; + end. diff --git a/demo/WebBrokerStandalone/templates/dynform.tpl b/demo/WebBrokerStandalone/templates/dynform.tpl index 097063e..7606553 100644 --- a/demo/WebBrokerStandalone/templates/dynform.tpl +++ b/demo/WebBrokerStandalone/templates/dynform.tpl @@ -9,7 +9,12 @@ <% end *%> - <% block "body" %><% include("TForm") %><% end %> + <% block "body" %> +

    Form

    +

    This is a sample form generated from templates defined in helper.tpl.

    +

    You can submit the information on this form. The information will simply be echoed by the handler.

    + <% include("TForm") %> + <% end %> <% block "footer" %>

    Copyright (c) <% CopyrightYear %> <% Company %>

    diff --git a/demo/WebBrokerStandalone/templates/index.tpl b/demo/WebBrokerStandalone/templates/index.tpl index f858f1c..5212c64 100644 --- a/demo/WebBrokerStandalone/templates/index.tpl +++ b/demo/WebBrokerStandalone/templates/index.tpl @@ -1,6 +1,30 @@ +<% template 'resolve_current_demo' %> + <% for demo of _ %> + <% if demo.Current %><% demo.name%><% break %><% end %> + <% end %> +<% end %> + <% extends ( 'template' ) %> <%- block 'body' *%> -

    Welcome to the Web Broker Demo

    +
    + +

    Welcome to the Sempare Template Engine <% include('resolve_current_demo') %> Demo

    +
    +

    Documentation

    + For more documentation on the Sempare Template Engine, please review the GitHub repository. +

    Demos

    +

    This is the <% include('resolve_current_demo') %> Demo.

    +

    Also have a look at other demos, such as: + <% for demo of _ %> +

  • <% demo.Name %> Demo<% if demo.Current %> (Current)<% end %> + <% onbegin %> +
      + <% onend %> +
    + <% end %> +

    +

    Functionality Demo

    +

    This is random functionality to illustrate the dynamic nature of the template engine.

    Here are some numbers from 1 to 10:

    diff --git a/demo/WebBrokerStandalone/templates/template.tpl b/demo/WebBrokerStandalone/templates/template.tpl index 1281550..e3bfd2f 100644 --- a/demo/WebBrokerStandalone/templates/template.tpl +++ b/demo/WebBrokerStandalone/templates/template.tpl @@ -2,6 +2,7 @@ Sempare Web Demo on WebBroker + <% end *%> diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index a4b38be..69e4174 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -95,9 +95,10 @@ TFileTemplate = class(TAbstractProxyTemplate, IRefreshableTemplate) procedure Refresh; end; - TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadFileElseResource); - TTemplateResourceNameResolver = reference to function(const AName: string): string; - TTemplateFileNameResolver = reference to function(const AName: string): string; + TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadCustom); + TTemplateNameContextResolver = reference to function: TArray; + TTemplateNameResolver = reference to function(const AName: string; const AContext: TArray): string; + TTempateLogException = reference to procedure(const AException: Exception); TTemplateRegistry = class strict private @@ -108,9 +109,9 @@ TTemplateRegistry = class FLock: TCriticalSection; FTemplates: TDictionary; FContext: ITemplateContext; - FLoadStrategy: TTemplateLoadStrategy; - FResourceNameResolver: TTemplateResourceNameResolver; - FFileNameResolver: TTemplateFileNameResolver; + FLoadStrategy: TArray; + FResourceNameResolver: TTemplateNameResolver; + FFileNameResolver: TTemplateNameResolver; FTemplateRootFolder: string; FTemplateFileExt: string; FRefreshIntervalS: integer; @@ -118,37 +119,54 @@ TTemplateRegistry = class FThreadDone: TEvent; FThread: TThread; FAutomaticRefresh: boolean; + FCustomTemplateLoader: TTemplateResolver; + FNameContextResolver: TTemplateNameContextResolver; + FLogException: TTempateLogException; procedure Refresh; procedure SetRefreshIntervalS(const Value: integer); procedure SetAutomaticRefresh(const Value: boolean); - procedure SetLoadStrategy(const Value: TTemplateLoadStrategy); - private + procedure SetLoadStrategy(const Value: TArray); procedure SetTemplateFileExt(const Value: string); + procedure LogException(const AException: Exception); public constructor Create(); destructor Destroy; override; - function GetTemplate(const ATemplateName: string): ITemplate; + + function GetTemplate(const ATemplateName: string): ITemplate; overload; + + procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate); overload; + procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate; const AData: T); overload; + function Eval(const ATemplate: ITemplate; const AData: T): string; overload; + function Eval(const ATemplate: ITemplate): string; overload; + procedure Eval(const AOutputStream: TStream; const ATemplateName: string); overload; procedure Eval(const AOutputStream: TStream; const ATemplateName: string; const AData: T); overload; function Eval(const ATemplateName: string; const AData: T): string; overload; function Eval(const ATemplateName: string): string; overload; + class property Instance: TTemplateRegistry read FTemplateRegistry; property Context: ITemplateContext read FContext; - property ResourceNameResolver: TTemplateResourceNameResolver read FResourceNameResolver write FResourceNameResolver; - property FileResolver: TTemplateFileNameResolver read FFileNameResolver write FFileNameResolver; + property ResourceNameResolver: TTemplateNameResolver read FResourceNameResolver write FResourceNameResolver; + property FileNameResolver: TTemplateNameResolver read FFileNameResolver write FFileNameResolver; property TemplateRootFolder: string read FTemplateRootFolder write FTemplateRootFolder; property TemplateFileExt: string read FTemplateFileExt write SetTemplateFileExt; - property LoadStrategy: TTemplateLoadStrategy read FLoadStrategy write SetLoadStrategy; + property LoadStrategy: TArray read FLoadStrategy write SetLoadStrategy; property RefreshIntervalS: integer read FRefreshIntervalS write SetRefreshIntervalS; property AutomaticRefresh: boolean read FAutomaticRefresh write SetAutomaticRefresh; + property CustomTemplateLoader: TTemplateResolver read FCustomTemplateLoader write FCustomTemplateLoader; + property NameContextResolver: TTemplateNameContextResolver read FNameContextResolver write FNameContextResolver; + property ExceptionLogger: TTempateLogException read FLogException write FLogException; + end; implementation uses + System.Net.UrlClient, + System.Net.HttpClient, Sempare.Template.ResourceStrings, Sempare.Template, System.IOUtils, @@ -169,12 +187,12 @@ constructor TTemplateRegistry.Create; TemplateFileExt := '.tpl'; - FResourceNameResolver := function(const AName: string): string + FResourceNameResolver := function(const AName: string; const AContext: TArray): string begin exit(AName.ToLower.Replace('.', '_', [rfReplaceAll])); end; - FFileNameResolver := function(const AName: string): string + FFileNameResolver := function(const AName: string; const AContext: TArray): string begin exit(TPath.Combine(TTemplateRegistry.Instance.TemplateRootFolder, AName)); end; @@ -198,12 +216,12 @@ constructor TTemplateRegistry.Create; end; {$IFDEF DEBUG} - FLoadStrategy := tlsLoadFileElseResource; + FLoadStrategy := [tlsLoadFile, tlsLoadResource]; FRefreshIntervalS := 5; AutomaticRefresh := true; {$ELSE} FRefreshIntervalS := 10; - FLoadStrategy := tlsLoadResource; + FLoadStrategy := [tlsLoadResource]; AutomaticRefresh := false; {$ENDIF} end; @@ -231,6 +249,9 @@ destructor TTemplateRegistry.Destroy; function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; +var + LNameContext: TArray; + function LoadFromRegistry(out ATemplate: ITemplate): boolean; var LName: string; @@ -239,12 +260,13 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; ATemplate := nil; for LExt in [FTemplateFileExt, ''] do begin - LName := FResourceNameResolver(ATemplateName + LExt); + LName := FResourceNameResolver(ATemplateName + LExt, LNameContext); try ATemplate := TResourceTemplate.Create(FContext, LName); exit(true); except - // do nothing, lets try the next ext + on e: Exception do + LogException(e); end; end; exit(false); @@ -259,19 +281,46 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; ATemplate := nil; for LExt in [FTemplateFileExt, ''] do begin - LName := FFileNameResolver(ATemplateName + FTemplateFileExt); + LName := FFileNameResolver(ATemplateName + FTemplateFileExt, LNameContext); if TFile.Exists(LName) then begin try ATemplate := TFileTemplate.Create(FContext, LName); exit(true); except - // do nothing, try next extension + on e: Exception do + LogException(e); end; end; end; end; + function LoadFromCustom(out ATemplate: ITemplate): boolean; + var + LName: string; + LExt: string; + begin + result := false; + ATemplate := nil; + if not assigned(FCustomTemplateLoader) then + exit; + + for LExt in [FTemplateFileExt, ''] do + begin + try + LName := FResourceNameResolver(ATemplateName + LExt, LNameContext); + ATemplate := FCustomTemplateLoader(FContext, LName + FTemplateFileExt); + exit(true); + except + on e: Exception do + LogException(e); + end; + end; + end; + +var + LLoadStrategy: TTemplateLoadStrategy; + begin FLock.Acquire; try @@ -280,25 +329,27 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; finally FLock.Release; end; - - case TTemplateRegistry.Instance.LoadStrategy of - tlsLoadResource: - if not LoadFromRegistry(result) then - begin - raise ETemplateNotResolved.Create(ATemplateName); - end; - tlsLoadFile: - if not LoadFromFile(result) then - begin - raise ETemplateNotResolved.Create(ATemplateName); - end; - tlsLoadFileElseResource: - if not LoadFromFile(result) and not LoadFromRegistry(result) then - begin - raise ETemplateNotResolved.Create(ATemplateName); - end; + result := nil; + if assigned(FNameContextResolver) then + LNameContext := FNameContextResolver + else + LNameContext := nil; + for LLoadStrategy in TTemplateRegistry.Instance.LoadStrategy do + begin + case LLoadStrategy of + tlsLoadResource: + if LoadFromRegistry(result) then + break; + tlsLoadFile: + if LoadFromFile(result) then + break; + tlsLoadCustom: + if LoadFromCustom(result) then + break; + end; end; - + if not assigned(result) then + raise ETemplateNotResolved.Create(ATemplateName); FLock.Acquire; try FTemplates.Add(ATemplateName, result); @@ -307,6 +358,12 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; end; end; +procedure TTemplateRegistry.LogException(const AException: Exception); +begin + if assigned(FLogException) then + FLogException(AException); +end; + function TTemplateRegistry.Eval(const ATemplateName: string): string; var LTemplate: ITemplate; @@ -315,6 +372,16 @@ function TTemplateRegistry.Eval(const ATemplateName: string): string; exit(Template.Eval(FContext, LTemplate)); end; +function TTemplateRegistry.Eval(const ATemplate: ITemplate; const AData: T): string; +begin + +end; + +procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplate: ITemplate; const AData: T); +begin + +end; + procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplateName: string); var LTemplate: ITemplate; @@ -331,6 +398,16 @@ procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplat Template.Eval(FContext, LTemplate, AData, AOutputStream); end; +procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplate: ITemplate); +begin + +end; + +function TTemplateRegistry.Eval(const ATemplate: ITemplate): string; +begin + +end; + function TTemplateRegistry.Eval(const ATemplateName: string; const AData: T): string; var LTemplate: ITemplate; @@ -393,10 +470,10 @@ procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); end; end; -procedure TTemplateRegistry.SetLoadStrategy(const Value: TTemplateLoadStrategy); +procedure TTemplateRegistry.SetLoadStrategy(const Value: TArray); begin FLoadStrategy := Value; - if FLoadStrategy = tlsLoadResource then + if (length(FLoadStrategy) = 1) and (FLoadStrategy[0] = tlsLoadResource) then AutomaticRefresh := false; end; diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index 10cd7ef..9ecc38d 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -61,7 +61,7 @@ interface eoStripEmptyLines = TTemplateEvaluationOption.eoStripEmptyLines; tlsLoadResource = Sempare.Template.TemplateRegistry.tlsLoadResource; tlsLoadFile = Sempare.Template.TemplateRegistry.tlsLoadFile; - tlsLoadFileElseResource = Sempare.Template.TemplateRegistry.tlsLoadFileElseResource; + tlsLoadCustom = Sempare.Template.TemplateRegistry.tlsLoadCustom; type TTemplateEvaluationOptions = Sempare.Template.Context.TTemplateEvaluationOptions; @@ -76,6 +76,7 @@ interface TUTF8WithoutPreambleEncoding = Sempare.Template.Context.TUTF8WithoutPreambleEncoding; TTemplateRegistry = Sempare.Template.TemplateRegistry.TTemplateRegistry; TTemplateLoadStrategy = Sempare.Template.TemplateRegistry.TTemplateLoadStrategy; + TSempareServerPages = TTemplateRegistry; Template = class {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} From 35eec9d5031a29c0cb8dcaa2dc40e5b4e2524f1d Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 14:03:44 +0100 Subject: [PATCH 065/138] Update documentation --- README.md | 1 - docs/sempare-template-engine-start-cta.png | Bin 33223 -> 0 bytes docs/template-registry.md | 139 +++++++++++++++++++-- docs/tricks.md | 106 ++++++++++++++-- 4 files changed, 225 insertions(+), 21 deletions(-) delete mode 100644 docs/sempare-template-engine-start-cta.png diff --git a/README.md b/README.md index a39f61e..095149c 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,6 @@ Simply run: boss install sempare/sempare-delphi-template-engine ``` -

    Delphinus

    The Sempare Template Engine for Delphi can be installed via the [Delphinus](https://github.com/Memnarch/Delphinus) package manager. diff --git a/docs/sempare-template-engine-start-cta.png b/docs/sempare-template-engine-start-cta.png deleted file mode 100644 index 09cfabddaba035d38f1a4bd496fa390dc60ff881..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33223 zcmb??Ra9Nc7A=;bL4&&ocXxMpcXxLV1b26Lhu{vu-QC?SxWhZ$_jcd$e%{ynfWzLS z*tM&c)LL`S6DBV!_6-IL1_T7;n}oQqA_xe$0Pyb?sIS1^q@hPizz=A9aSbOB5cvMT zen69`;IV->A)O^;L?Dkq!C-%o@H`H1fq)QzNC*okyJwzfd1$M+e0=5{{nSpBftrtt zNY+P53_lcHrCa*-jb(aNkh-c2hb5ycAw4Q7p)BcuwZSrBR-K}7_N!4oYLFBRtvZa% zXpud%vC_)r^Si^P>m^u<3_ah}P{48afz#%Z6E8Yn&8;r4V#>BIBbYxw0U{Ix@Z%5U zQ6jG%*mOf9;s0*hQy>wE|91cZqP7AQ63Kr?1MgM;2!ulZ`(OVWp!Qu50{XA#{B`k@ z3Wo6KKNny>!@T(>PAP`^?_ z1qetta`ln|`5MUFUw_o@JbAnxE(u9FifCj||9z1Zj=vV>sHNlmC$?uinS)WivkrFvwgC@fI`a123%a@;Hacym!<>loZ zfUfw{^71BDvA=h)2X&kjU4Z7`-9a=@cWOAUK(olk>h0}q+S8IlOCp$YNk_ExB1=FeJ9(6D1h zks>ri3TZsab;)H92HaP|!2ejF0h$cSSwhlS4+az>yqSW6qD*D=VcK%W(=rGI87e)L zdhG4NicnLI22ayQD}(rN`{sU$Tjz#4#TV<=Si@*zkMXTiLoHGbE0jpFagw`X%_esrM(^yoL&1Z?FT zzc$xoWqbW;)GDQTuUfFSf=eO6u>~3zo`>o7aDpkN8Gi93mBp!8ayWHVAN-Idpz?7* zPJ+IMaQ__Z`udtilcn0K+4fj28+{?C_}6tqMAQ<+c`WWgNWS@Cs0QpUtu!4;AW2+aQ6L0t4{jU(#lFi zMda_OS*5_`bl*PMfR2IJj5UenCoYR|aC2hv3gmpml~aj|jEp3a7_N&9G=5y6+Lfh* z_nH0bwx6nsB2eoP1?xgJe>Sm+EB0Cm;94~s+a?UanOzzNCyvN;u$-u89v5(9IqT3EgU zRW!7dYw@HDxVkDA4b(|)1u*|y-*hanBdDq-A2MMWpg(TMh*)b=105KL%NO{nxFn>c z3{H#VO{`~iJwrnwvbwtFu`}E1Z|cO@JOVDhon_@o*l%&~pX;m5(?2vPP@E~sdqB&* z3EO>A#I66yH>>bUq`QKhz{q^eT{^>LG^VdNnr(sO77ddWuU(#3OCtLdw%Z%V z&_j)xW`s_`w;TyrePsR$)hfgNVTJyoAtt*`?%fiSUzQ7$KV3M1;ks(tw7^DZi{N~` z?Vpmu7B#VWZ?EP}(HO#}sA6>YG=KXl_y9>`=gnk}ip1<5>$JmQx7mT#y-A*9?a)@E z6WBkYJ76~F71zz0%r%4wXiC}Ms;~NHg{_Dty))^cl-k``MdJ`igt%o5*>~uuzx^Au ze?TU;y!@c|C|%kFkE^*b{zU}sd7&#%R(+VuPR7!z0zwwrU{x#4yMaD9)V50*^&k$w z9rL#LMcD<0q_AqDUGW;mtRLkeqa^A|&TI72i~E}D0e8~)UC72}gtY&TZSpRmEn>Z? zsSH;?3*5GyTN0R8(-*A7C1fp}t39RkJu$aBy;! z-WLKEtnsXvA|QSy8}vS5Z0fxFYGvf*&GiHYhFi0CE)X7X&PxI7f zl8b%GqZu*X=KrSog?IImWg7paq>?k6T84 zB^`6=p^PbAg zGKXM~6l70Rj;Y1IYvKF^)-3(C>=zuR)D5`3PIhPyrWGrjfTW4Tv()C5CaZ-em+s{| zjq}}aqpT`h8r4=qPv}qXYXNunKVuml9<&~l3c5OGp=%w$(}|HRQBt-?UNa_EiIz}&ij z2;OODoUT&%({@Mw#Y-jbRf?j?zvh0cx$LUx=7C&ti}S7PKC@43Wr*F?8~+Sn;f2|7 z)Uj*Xh~`5E+ftrjNHMssz2xBK6B+r5SA#*IbZ_xK-rQa76 zy~}G5vF+l_gKICAa(w#v*2+VAM(n=RoQyK1SoU-;G0jFX( z7|CmDE;;=uNW7P`={7fJ{F*psI44+>9yvCLvk~3?l+DP+?VhOx4v051IDfrQ*s>=x z+C_}g=XZ?s@uTBAgYW61N;d8((H6K!%5t?osp1m9Y$qDyDr5{J|l(skwE8O6H6zlYB~EVrUC*#&0(hI z_Dq(F)5(#QQa8`c7SmU;o3nO#5mv*8!M(vL>YMydEev?G)solMiy=lD-0aDgD4TIcwNckYn@Z|^Hv|nml^wSCP^mqca67#) z7^W%>bf=Qdy9t9)u;4N$NL}h-T^|-5$+)uSX-;r{%piN7;Z(>E?NIA+r5wxajYeM) zxkzz7`zM6Lt2BRae4^3y`~kD2Hy1Tk6P{aoLhWmXOdoLY8ClW|pwPgUem6jgU#jIp zkUswXA*Up_CHJ4TmYiF4jmZwO`Uq6N|UK6gmXEXydA+GM~nXA3Q) z<*N*B@lx$aSwl;^tUASR{>9bT?fZHk=CyF5gA~-<*I=P%&{^MFzOIi~)?JiI5Z3GY zR4Yt&lPVe5K97^Cle@=qBEYQ@Nrw^$LY3KjFNU+qiZyFg_a?)*4i7r6*m7F>sDC|( z%09pb8|_f)Gtj{tp$K@8u)c8W36hVSwAO!w-(#a(5u+MQZTk%TSljm z+_pP~Fo(LxZQ7vCjiY|MlEytv2o7tcYD;ZP zZ!{waBo7^4UhE%|BK|tno$FbfB z#@*A?Ppys*u&O#IiyLav8!}_t5T{mGkj}S61WC=cJl(~1C zoaXF1%w%5)jXyjyYlDcq*!J3Tec3BU$7e<4K6NUewm#7akaqenE=+#HB_$sU*AEZH zayi)IvKAp>Iy?pLH_sNr&-@Erm1wZIVNf^V<X-pDi>@-6$!PZ@VP)w>+vs%=eiph6er<7600rS{_Zf(W-Vx>j zd19`4555*TgHOyZ=EbzByB%MOz+mx7sH;7~;a%kn_z)tyCdqJr&pUy;g2s_C>}5BC zQk!bEUg6AwqEnJeNM)sjyI9fbupg3U1-C90YzELO_!NZgFn8(l&nPU?NE(yJHgE{@ z7&!4wRDZSyq>0un>|rBZ@AH!a4x^I(jN^jS4B1Z40zS3#oehM+&&a5aJrn7U=h9e! zL9F7o5^JL|IV529>qiuL?atj5SJ;ob;WiI=;@+L{rLxLBLTm8o(@wkFXztk~YT|nc zS7|Rh4iurrL=&35xaW31M6^xZAiE2N_}wdm8E8M9<0gQJ^i^JeePq#;2@)$w@Q1%j z=JNwZwx^uM*nS>+{T_vFPN&o}{jCu^50XUxAC6z9L7kvir1YmeV?*G13g@Yag2Pe2 zO}Ep!85oqMR9uNr$g21ctWw#tb~+m~pFNS9r{o9C6m_6pv!i!kw2btfV7p0f!VEXB zjSkJ^lN+UxlDM*ouFrnxVzFdCsU;WSVhJx?%TW82@w<2$y$lpxWuI){P{*5+U;o46aTn-3NKLZ|4V>K>6Mo z&%@Fr(x~H%><{-9UGQyT=p>^z6+3C{1>1Ui{TP}gQr7pvl0syxTV$4|3O?lz>pA$s zOiBY!I?{25ftfIX{#SUM-`PB`rDbInR!Y>%(un2ZuGcRe$ta~~uKSF{l;NRbUAyw* z3a&Q3DMeITb0|&8SsXDaE>7%oUEIRsG40D8wwN^~U^%!1d}NW+T3b8I+1zy>ulUKd z;*PBc$D845PnhJhhSQ`6t4uodR({Yys}5Xm7}xa?k>rC<%OM2x*loy*cKR7@&uRPp##5 z8a)QPVu#xFI7s(}n}Z&1%XaiAlp-Px9b2TIMQ7TwF?^Vc4Ud~7m4Lt7E-ebgaiGa_ zP@p1qc(Nd74xs;ijts0>IKRN}?vb@8>p=wy=>I4OW+P4ne#eZ-n0Z~w2X?!k#g#0) z-R8Z{eqQX?YcWVQoOuBO^kQ10)A-9K&n<`SFXTkf_YU!6^Gw0yN3w)%a&*qTnhm4lXK9jPzI>M0{Vsn71e;ew$zd4IM}dom9orkSvyqujp;?Mw=M1I==jzP{aJZJ;$kMG zWZBc42>W6VmJ9;3+QROf%3wW71hAjgr&kY*EqJwr1%;?0`FZEr*+=xHN~7iX%5SCt z|0et<2EYyL02Q*S08?WKcyzthJX})UOg!PVRL6&|)RG`zVi0+>DvWX=5i_&9HsN zYr&5VIA?fm8S7$)fA-(|P5?s8G2QDqe|{Uh9dv$K_tj&C&G8Pc73vLbGb6;mM>vY( zvL@%fR~Ynn(S}?c8LrgyUN8$aBCf+@(`^Ikx+njZpjM z3aLA#d6Y5@xc*+Vw%@3NYQnZS8(h{*FNld=0fL`f9bO)MGj(>6f|gs9tp}b;*;hoe z^hwv?0$R1)vivp14h9HX)mQl8RL;f4)dr|ks4h^sXF8NY1UwGhCg!w*6k)AfaZQ&= znN2QbgKw9^^LD7Xlaq60AKmKnAiOO{2>F|(&2ZofRf()?m(m<`EM{ThBQR%e{CF(w zoeO%kqyjofB1$39)$h%tG*9ju%k0Fm39MiwDJMR29Lpg(-m;Pdqj1+@>E2y0HMR+) zCost+OYcqxa+5V9hBUl*-mfi-+3PN0Q3yYD;iIjgou9l^6nBrS3Q-hgll(3@+qv_X zt3T8V*P7o)3p3{H`hWh$O%>`N{WaY7G9T41Q z3YoLz)(JkhD=7=0=C)5#Zsi^cNlwP45LA7Ozt#lkC*>;y9 z0G_Wfr-Nsl z+bGg>ZW$lTyOI*-dfr0RpXsN?!Fk}LlW*tS%dcUlRY5o4m08>gX36Av_lk0B(nj>X z3IOb!ip$dvoL|p)MP@pyYQZR5>3yNUDIL3qcOzLNQ%h`a?Xc$PXnh}Xa#qHVjyQpk zxr9V`_hQn4+Tmn|7!VezOk-M;X^5w))NM>(s6dUIp8eUgpLbpTjQbca`SuMt%Oni1 z`&(f<83aJaj2V4H{QMpzzAGB?;bD8s_S2|Z4=Zb(t88`Fe4V`_j$Zp9pl2&28rMpB z$=+FS>7lD>0s^rip4@y2??`3abRu(Q7sh*0x+Kl#d}ZyS2r)-|oxhp+j0lt|^m0$6 z$F%@`I5h?(i#;Ie@GS|mBMmBMLR`7G`bGUFzz|(7zb3_oJ@cDZ4zZIp%*G zh{YQ$^~p2NtD&CO_dp7co$tH{yMO!mY;S8;95vd&S;wf{RGQZ8)-%NN%8rb963wWq z8w?)g@ZC)ZK$DQ#SX(h+4S9)8^P!3e8*}Sbej`k=4@R)iF55aj`jqJ~V=c?U_v;dx z@c@1PQq>|W#!UDA>f7+DD8U2uZU-X@dwegpUE91oTN7)u*5pt;Mnd#f``zDuNB7?D zl8B8gGE@t#&S=lx6^|!MPagwmNwxN*NlaZ$@0F&1awTH!AL2^M6rD6)$XH@*!Vp;Qg2;hnvp z%4b`Z$=arca1;#rN5a&MPh1clpEHue(*gY6=9if3N%D7!=ZQgTx`Z@sI#-~=HxrCS0)B$WUHo-SeI=$ z70?~w*zt=w?7FIKhiS{gy31FN$WUkjEw9A)E3S{FLw*PF#|tp7BfinIHn~)EmM}H8 zxA8y(rS!=qF}6%i!I zM>Ff~1WE6JFhgN?ZY^E}g}@j&C?vMmIIlS#?zxs~a|{^*g&OK3&)Zvc zNCkmhIkSOt6JeXzOpBdsYQQevdi(6nx%L%$#cTCd`%x@dO17Sqe){Xc&=8w_p&&5E z);(^;B_$14W{YTZmhjX&%kMi#8(kV(R8V&`Rv`7@p0G$bvz&;Z9-+M9(4m=!*LBx$ z<=oPe;yA365d7f)A3pSaU(fdhZN4yWA0mln*@+eM>#cXt?K;WI$>{LV&b#tg96)gN z?PK9^I3~^+VF@WJDt3l%gOC9RiI0JDIQ_W4IDmkcUr^XU1J?=sRDL# zQ9UmerU|}TwWVP49_Cz-R71=d@Xd~*)Fj2~2UseOzc2xS^R@cU7&5~)J|+B<=K6|lrVO`YoR?JR41H#QplLU@7SJ1OoGXsE;dgUp1B*lQ1YNE{S z7;K~gBklTGn8|o%jmh)e-Z3A2`jfxp>r3b0{$cOSsvU*v^|;Mr?CYo^9zL$Dym@N8 znY@Zf8m|tLHu+*aJzZ>bPIiB&|IG1MumdypKRHF*_n8#k1~gOL=;P^teiR1!^weR- z8$KB#D`##6G&770e4o_Z{sZZ5(nzGoKu~#=c(GGRkf6*m+@Vkkr|))w*H}~PAsI<# zaoO0?cRF$A8+p>*5&as2M3A>Ban2i#yLO?5hW3Cvy^;h}M5BZIl>w$0TA}t`hy0od zqPCLRtyV3$c6}|Ly8_6;mHAV%RBtQue1WU+8&!2E~l=A*AZpzR&-Ri`WWlFhDF@0;NCm^Zopi- zIOepc1t&DKJ&42a0l2dLffB-rIHVNBF;7~3)FJ9IJ=no0PT=ys!C7e_8Bb%x0+k*v zSba5^ll_}N0N5YRGFftqita%ShCh>R_7(0Q!6ToHESY=vZ!2<-c_03OK443 zlOgtMqfXGZ-!3^sKNb6?YDlL~oWGym0|{AdEDhz#PEqK_FDwfSEmo44h?+7-1)!NJ zbla+GB7S+`LT6x5Z0@%OQxqRQI<$~hG`FwB9~$l-jDe0CeHLusNxGZjot#Q;NeH+U zB2EtwEDIM_Is{sUv^|~s!3b8|Kz8Z=5G3uphZ1@f%;Jf9le0zV5|9*d7!f&z*$4<2 z;oF2?5^BXs>y)IhxuAwD4+_=k5TH2O7+rcUU@#sgsH;a%R8d(tZ)(t_iHq}D>GZ0x z^fJG)dT#vnYvlqKqH4-{@hV5^y9PCGECE`%^^o-1uNV)7*!x+2WK9aaQ^)<7Rk~si zeI@lq&jnHq=EhbIfs~){xZHmpABTkYCZQ%|;MkG*W6y&T7*#G+_V3uo%r^NP%8TbO z50EU8{?P)g%Ur&XUPP&!OEcnt{hN(lt~Wyn3hL&pIz$6@COGR^Jpqs?=-6k9+-6=- zK`6_N(WGoArEebXh-4QSRUu6x!m%|ma($40lWWuBf8kGrHchvEO_j*jb=Qhe)y0ZR zN-{1osP4nwDvvbeU!BSKn`MX7IepVHw;p*ivTXo9(Li!1d>EcUy5ds*;{ghF5I z8<4^QKqkIQ@m*+IIWaR05#-&~D2YqJtFW>%GdH*52Nj)_t5#PRuQH2z@!vv)pNB>_ z9qLSu`=zX?f)^SfX{D9yF(KV(nBYTG=#J}CEqjK2N4g#I(d4c6y3dt~OUb|iTbhSW z7*%NSkPiX1K$aHBpoOCkOG2f!G(9%nH!aDBX0gyzb+o&l1%K>ATUxET#i#I3YySiT zhhF@mq^mlZ#?-B(vcVi94#ZdHJ+TDW#5pp`Zb&@iTi}?}8BPy!0%7&V^HC^+2dvh^3(zp`fYm021rhH#hkW^Soq=1W&{U z$khG<{()j|tr9k=9egjnf=M7?2MASv;QumiE()%lS6&qaRN>^E;zjZ%@T&mY=XIYFA_+!Hb|}rAyg$#X1%jNkI}hO zodDV0rC5?MJT<+ER;ME6X8y_l=q!FF_l=Fg($U$)8Z)<$1;PyzNC79*MgtoLZe`Md zVim>w+4Ft^O{HehyaH>`um+v|s$zsHhg7^3ba_R2X`A{$P8%dK%bdy&MC~x~_UUS>p(&OXf#{@w%=1z&AaA5O_ zq*5HupBSQX`9wtr;Y}wUySB@y&R0511{U!gMJ+5Sz6SUMUq31*2mkW23QOh(;v1p8 zo7>RTRLozud-M?ri~g3eG#FW0mUE~%MgyaeR=b1WMkk9Xw9?n|?6k%H8jO^bG?R*B z>5{64V>f|1t^Y~L`%TH0+t9I!k0^W0GmJDa|}RL3$X1p0(y3e$h*sbczxhn zrhU!tPwoHJ=I~GBwHPn#{6{zPm!c+@=l>g}xh_P563Z>MTDo>}Tw*(1)sile%{q^!m(z7Shd3F{>|l)^E0i-y)7Jt2IbiZ?Y>esLrtdraf;BS2 z!P3|$e1|D+p^-Lc^fCPJj!Yj8Lwx-jANPT&(+08NWvruvyR>1yhsx#Ql2!abp{;lo zssPfMs2^uyA_-vRLzk1A@{E!3-owoXID|Pit1YWDh7l-W2>h-3@y{KIO~ThOy&K-C zRPTzMYO*zaO<5E7{L~>El;KY{slb0S71InzZ~x^FSGjJ(bMX;}l>J|Q z)=yLW*JZ6%2YwPlLQYDmskyuWF1HPbj6OtaztdAoyvz@bH8*2=ic8y%##5Gdqy0{= zI47q#BI3B0d!>ttvNF>AtEj}}Bt6e75wAh^vYhX1c{-0}62CvLmXX>K*%Q=jn}E31 zpQF%^#N-sNhjsO^Gz7jUwgORjwMOz9Jp`ZE{4~ymj`!Xm*t=uY=)yRVlkgkrbZ&us z&dmRKf`Cz+30MHwO5-~$7I}dp7$?bniy`@h+{3Wij0X7fyv3>pJ1ic!K0GD z(tf!eZ%<~f01krCQwYXplh7viF{lXXn>2jdzH8WaLNO!^9bSv!yZ-Rozdwr;6jFJx zV8%N9c$dh29)cZ?4-AZK6iy!SlM*Ai$hI`93z{w`V<&d4!3R1__dSrYY}fe`GP>!jk~^U8 zM7=44n^m-MS-sGpZ#?Jm{Z~$$kuB5eS4t#f>)S3b^6SZrPenaC1|$wXd=4(`iP<9Y zfUjwfJ{=TkCo$ac@je*<%(xSk*zCv_2Rnq}O27^nIAyp}#Trtq?O z`s?a636uJP0Jjv?Zecoax|z;VH`}sXwSpy^ZY`B81Hh9=FS223O6I75lzuQa6Nwv< znNhL2brgJ7yZ?*j^MRI4nEfA%gF8dR7~3s2KfSuSwrnNZ88*Mq$<4rF1v0x(2Jage zhQs`MV7*#0uiO+vM<+fyAS5MOKB!#YJv0`%hs{Q5;HB#B;lc>nL(xsMD`ufZMzqW6((d6AJ_H-v z?%G&inxK=RhW4-UkzJ%%N*+-R0bgFOT1Huo*vwep{ZXe7k0-Z=q2Z$aDmEe_uLVO$ z^lQ2whzYVSsh>5lUK0TY{idnnc$s-{(OJ0cK~Y@m;1*+cSBiA(%n4LD>H%JH@qHW0 z7HYKk&;;l&tGSfDu(nDDeG~PcXg*`S$Om?S=Fog`jptigwMM~D`#`+BWSzU}^h(+i zjVs3FLKD(VKaH){X)m`{vLMJci$UacYeG8S@(JN~E}c_ws4jdqD)otmT>4jf`I}HO zdt|fH?);0q3rlbmcAS!8Za$dBG(q7=0&1R;g8vS1DVJ@qKquWFmj><=5hi*2q6a3W z>!mOXddPQ_g3V2!WDniw@sdY-^zo69u*W;0Hp>qLu<-B%Nmg$;P|;5=P5j=ir-Q6@ zPVXtykc|h2#Vp@hGvfmT%rveS;^aSmOm|!XT{o`9^otUncMJ5R#WmQ5@0$7oo{%G> zqhyYNo0j+bz+N6PHnJjpzWlX;5$Si}Ve^onxxQw+-fZlH~c8L^i~tMSIol3Myj!^Z;UFyj^=!GLXtWv6VR?zsS=#jCePm9-zK zxZK{}M%}KX>a`}$=jGIj>iYiP#BPy|64W@~XeioR`i0H6t1F5HNvTV5saj|Lm69@X zOLbvw|J-^%fm|g76Yi^&QaP`x;EpIl!o&nVIc3bV}j7)KchM9+{%Vdq<;&OaZ%G#A3d4Ow?byge2puv&i*dOTZ zfDf<`1bl&b%}<>s>M>~|IgPo6z1lqhN}ay;v>W25Ym zaB!<&%b?ukD0O{2XOh#RY;2?#vcu{f$pIMOis2)E{x;slPaW@G>Y7N~g2BM%3CX*Tmc8wNf&wkay;Oxt;uMWoJQGUJm>^x9!XHB9}Dp{}bwcYX_ z8$@8sTUbqeoSbms#1oCei1y%KR$fyY9Qe&jRZj4)9vph*=C$|-_LQJQ?fW$k^V4q%WDA}s!qH1%xl=t0#SRDIGSHNGV%n~H3P(jjS4B6)uD9hQ8(W$%Q$e5Od zfjLJgFO@Hc)hvjZtbG;A^x8O=a@cX8*788M&mseD$2r3Vl-J6D6%>P%T(i^6~iHXG&8;MQ5 z#7vAU3r|hw#U_9+X5(f^YLgY(Zp89%CyulvLGn*dsxF2zBrR8m(CYmB!J%c`yj@IF z3~^sJ_t8tP)s*jhM91X#L}UH7?ZfGEYP`wmG7A1?bq|A#=bhSc_sMux6Vu-CqcY5@ zx;1@E4xM$5b)YLY7E!U;wuwp_l)%i`uk5@9X~eX)aPaQr=AecN@S(lHYH_X(Jb$?u zymofK#F}{C!R^ddjmR`l`Ijay)j(!UH*Wb-IsSrAEE4XOVf&_V!C-8h`lbEtvR!;< z$8|}hYSYL%CMF73Zhd2~)yCQxHZmevX#bzo z2eO#7xOoMCX{ARmxK8nvJL`mV$;U^uQ9a=RK5M>OTdLO=m84Gq=a^bRqE+f{X!sw}*sdTQ3v z@orE=V9{N1Gc4x|3N!b_UCAu7K-~7n!$@Pt?WM0@z-@$-iIA0HH35jN^FOiz(Mp z6o*n)s|d#vH!wJau9&aN=qtB(P5DI&(BA4T8yUe+R664kUAP=c7`CvGMVa5ydt?P3 z&HMOVy@2?SiPE^f3vQG}XJAGjuW?((uXm$UG%rQAJRquGEZWOPRXY@kp>gvAzdVEc8rla{L zD0nWs_FHY&m{h@Kj->xX?s>D(#_4bzOzypIBofumsdlsZ z-SJC`%Lj#G#wX0&ot0FBm}z$?VX=;%Q_$yOawWI9ZK<3OkN3x>hp$26vp3C=s8UkW zQ)@$QZki_p<$TrVU%%DCHpY{LO=fX%Sx^0PD2V5ZkKOjZD9FSi)f-40QdZdc^X{!= z;|17QlMd|+dA)2Ovi*j|LGz7+LE*%x;8$F4Z(=Xx>jOsvy7twany${|UJQ>VpgYI+ zv_&T)@S}LOB)=4@qXQ>=(?kmGc4lZKF)5{@CL$6DsuYMu+pia2Ym9h3_HK6SbKzC3 zS@t^Bi-boi3p*MT)up5m^;V)G+FHMkwz!8tJ3L}UP&HWl{?7-R?0~(4Siy5k{4w-{a?wWD|y*x3? zCcDro_?r^x)!4wma9EgmN9S4A-RR0nYX6s2<}p zBM&g-1`|2ttdHfH9ULT~^i9d!y()xV<^~6#WZyl129_Ztd~99NPzL@B zUC=^0(leU|W&5P?w;^F{i--&(aD&Xu*x&t_pPxUo zF_fy5`;-J9XH15`wCYRIYxQvi*DmF|ZNh_5_#3hA(DJeJ9r$~19_a_4p=pN=T z#M0>)V#@P~uTJ1#SlH zRPt6;RTWh3Q)6!e5$d+tUj|W>AJfXO<4Z8GO1<9@@RYfQSUtp-w0&5vI+BJ@^1)!_ z4r@IRtRBG3feiJR*m&Lm4GeBV-csvI9QZtm}Lk@ZI=`3zH4ZJHd5>cLu@z{5pX z7KV|z&4(D5(|P*};FT`|OZva^7<1%DFp06mbLXFD^yLN%rkp}+B6jwckNS2OrEqd% zQ_4JnA9;K&nrQ7}2&9Rnpa_BAF)h-lA1@*Xd)9nV$Q+38&q|;O=7c?tydmd`t=xBQ@$GAj8$1tF-q;0{Rz zLS0~B{|Fe5zkf7UMH&#i8W%y?@8Bl2E6zT(b#ZYyr8HVtkZi0R7B`7XKea4TSuXT_ zk9~dkQs5rvTOB?@Yn)$H%Cqcx$V? zk`XO#l|}=Upb$f)UJWJKjQQFrEPBP!hfY3{Pr2om@mx>9y&AVwu#ciiks<;emlj2H zc5H6oQWocdy$!w@aLtnJE58gR4Zsi(_LWNXk7R+1CgOSf<(O$9Lrf0VJ~LHS{I#_d zYU1Y?(;4C@+z4gmeRX3NC+(K@RxKaCiO)IXeaAQxDLEGYv+GK2~;J8Mfy#VJhn-%W87p^I4{5hx9* z2|y$z_4mb(*U)9Ng_(?k9c4woDytQwF$8d)ArFX%j0$F8pa%e9GhPp58R^Ia&qb5m zsQjt*1lr}`6m&&x?arS9Sgdd>>N{10IHY@QO>#0E9O-eyNvx3Y{l9#9Y#%qwjy8aH zi3J1GhM%l!lA9csSHpyZR_LJI@+rBQURcxB&7Lg|flLpy9L(L!clQI?>CZ z41_N$r4M0kds&D2rqXXB9a*jigcyqM8f9b?!VV)Nz*jibRYQ}~v#?11@d|(FdNy%J zyVU!oq1GKw7eWZ9;l$?Vg9(>z-|%63qMxH(mZc6f^f0*`Wr9kiZipX<2o4T+=zL?v zbNZ~$BL&KQzpw~~M$$X0oMu$gZ|Q}hOlaN|Yo}e(1e2A_0^9ZyKM^uld3&GzJb3*${lz$*z2&B7XncPhhGSXEOZ(R+-skJZ+pp07d~a8OaQ|CxCU(Ogd{||%VXsG`k|GMc zuSVHXnZU#GsSK@*Sy5TJpN3fUcB8rdy!TZ4BCUf-|E}x%cYXC1c^#9nQ;gr|{*2b^ zrJJ@a0*%?Rk{fLX!(-rc#tWlLxoMm#X_e^cRGY|V3kNR`jSV?*c^w&Uh-io5Hcj47 zGebbMbI4`sUBhL{(UGyRXeB6Ygo8t$`Ytz(X^64bPB3SbN_APq13XqPTX|BWO}SA9 zi$f8qFzOe=??1pM!NDQiZyeaz%riVhc>f^?7Q0?fy<(vAy+`@wrK%Zgp^FCJrz5Q|B!<93v${Cr zzlEl@XiIDB&c&WkiL9=>IS;U3cPqZKss?jo}dtz`B!&)4I2+ zki%&@Foenj4-_=IuO)J+nr=jU;c&UO1$YYU)(_A4h7^pfVVC9dr~TGEfWuT&nvi&$ zXuRl);XUjLDZZT&p(_=c&J_^P%bVx<1CsvcUC&OxcNMd?Se0xx9mtzBH~al6ENVIS z>o-M5>xavMKf4i<%UFZZ?K4bs7_Y#SO(gXTgnEa0@fTGY-S{pa8ba62P1z-cx<1M! zu|0F29|#D{Qpxg<(?X^(Ihi>%K!%!gap1@_3fNT4HjBcQmC+jpc8c2yzXJC#ETs7J zi^QxSb@Q^Oz<;q&`!qy*6@sky_p%>!B^{NK5$JUCYHA_W&C(;w#Y7UwwzjtJp00%n zDd=inz5*f0z}>B>rZOFxp5vJU<(z{^I42Qm3To+*2oBHhQ51!hh5hkG&C-StKMdj>SfVC35lx(!))F3x91!0did`FqGvNEINJO(^2 zFWhfnk9ysIo_2~n4GLg90eey<>mJ&YD@l|H~pI=?MuREWaHYtf}yq0x|cwk2`yH+ZCgC!z%g+hsl zv-*dJONvHyB?*Z+(mZ-{&?Iwu&tnh~Lv9~!Bm5+GHSr#(#$D4|q5PYIqs8au=02Xi zms3_#bGS+QRM65|TO=!sHA!1t)ug=CxV8RA3qUzqR{Epl>9HO{8#R=+ki10hUDQSB zEWSbNyMf1>d$wi82oph4Vrrk~z8}cJgs&!+qkwMm+5qN3WX#bnZWoz$YtD2gM-{vH^0p#(&$kofz8IZ%VEwVN4+(x&?d2t$He zm8&NE^8sDMQc}q??QR$&8N7$9R$djWOn!>md10=rD%8$EVh8={qM7IhFbRab70fI9 z=A|F^E;fuo?-tZB*I8`2#gXF+1>#Al2#ujtY~bT#086S~KJ#|JK?b*Yp#3u6`C9DX zqTX)0b2Pdyvlba82wDU^qRu{D_|(8j+x52F@9mh&lA@vc7%W5a?K3w3?*Qf_Tsx^I zmxF6|H9C*zGavuY_mC;wcP%Y!5_1utJ)V^8>46+>ViwoZ;vqB4)@0W8y z*2P-u^&mF1{SBDMhS#U*pq?4%^ieRjyT=N0b?YJ48|N$LL<$8R)lrENU#)(RAkbT- zuQm<5-*!ZM`)Zt)4*?(bahDtIP8%+OVj!uVKn}b7*oQFgVb~rE|xN;RO3`U|3dQ>)YxUmhL% z%-|w(lvB~slm1=^Nk5_Mry5qKXDgWP@{ zv>{zc?A}}j-fi&ZvhgZSXY!8zv95uv#6sR5Q+)g`ofawpc{@91M!wGsSrMbX5Tfhp zdP&^IiE=kNn@9P0ojGus-{J4Cb*Uf2`Obv7g9aN#dj^-g`N*I77E{XXwR1&^=olFD z+fFXP&A#oP0PWhU>gt$3SY@z?7YBFPb7TJh_z>up@9qx#$)MvJi7TkO+IZYD+>k#S z9Zlf{;^=xArt|K`o498AVSg_>l*MWF8ofA2Joc5pZ@tKWY zA8&5E6dSSu(GK=U>e^<2bSNJ%LJ=1L5d<&9(okNTwLsU~@*;-@Y%@*F>1pYh3Ro%? zuM3i51tqvTv@Qp?NFxbLG{3+f1p;0cW`>^)8$9@O;i270H#hLsILx?YTXy*C*B#K9WxQ=uzv)YYlo zYzq!@1euT*r8Mk;Z!rob-Y-MfU_FtLr{cUKG~pXyLgxGHeO+BuUf%4a$IabVwmlV{db##M z`*Y!U=(q_+K(5(2tHz;Tn9VNH_LTqg?P*7LYF$=NeR+hso`!^yKCz#lpIHOp{NiG4 za<=KisK%f$>6b*PIYPIDrY)VJAtG-fAt5l_$Y79FT_J;Or2YBp)9x6X!?0n;g)Anj z5iSu0xDo0&eE?Miu^HPELVMO^#!mW1S7QUUKW&(y-t|~jz|}=%!M+qal+qNrw%3ta zYp|RB=|$&-Mc$h8y4+CP(M+47O>2Vp;6tLi!LJREjMY!B4vG8jy+_fEQNUA#Y*2bS z9!OzxKg$9-tZ|1Ed@&NJhsSyb()D zU2>0?8{A%}Y_#eXJ7af_cvVqO9!Tf4ngJsii$-2swPdt_9&9aYs%chh&+=U&d)qDw zRF)hcA4;#%#-sbhvg75Ne4P^q^l-_2Sg;zqhXFdZCQ9iHIx1olZOFaegj=${v8wh# zEWN*VW-|_V$Edw0szuL7yX2ma8;^&b%J;d_pp^uV1+<%zEv_gzXbGBHy=dyl_PFfW zx+IiastrZHr(fL&m7&)f9RY53gk&uRmJr1nk*^a3R3-$r^Q&R ztB^CEf`N?DSnmFU2sT$|yPRgVcb%6em0g`MN~0y1^`|$5$T~2{Wc^rNTZ@hL1Xyt} zb)+pjdkpNG0s?9Y$(%}1!l*P$DFV0_+#ctw0-GrVD-!UD!B+?$teop$T~?o8yhsE1 zK*2*m+pwXvp^(|J;WFc_P#G2$G>}m>lv|?i5~se93c*uIe%_bOVlmr z1_!!gooly6TcA^{P}_6i#rXW7(Gb;7t?bz3WXbWTUkw|jSLArFa`rNf4P%3lf73kE z-DO86;*3`4BFZn=H)cotAqFT`)_oI=Ps8}7q-w|G3K(x^XN@_$44od%LjUNf7W*G7--@B;}7Md&*OBH*IqteU`YGm)7_dm z0QC@NvVMFm*H+}8Ea;fAH={P`)sZHy$NBzto0$0Y^rB1nU8=WTA*R7BCMF^AG?PK? z^K-53v6-rh_j)gThoct^@;jtGx*Cdj`LAyk%l&}3g~G@*d6`=2$dSg?iNRL$()7R5 zX;yOSl@kZ*8>64bm9kH``w$JJPm%pHYi-)iB zYI6oa8Ga45lyn}(Dq^sLk3PB8^c!rru~IBly(xb-0=RWk6EqJvXWh(uZ^C=19iC|N zK$C1X7qxD7C0*`2YN0T}AYdoX>QGZiV1c5{DBIvrH<=5R}7 zUm$(DbofNSI@4l#;lcLc5yAPh!0EPzjX2DP!mIBhr$q8t+%(xbuH7P?k)tx1lEzz) zk}!cFdCOOPMEFTk|p9FD|yrn*iJP2 zaN}1UEKz=Gqq}XcYAa5!3meyR2!(_8>Hg&q#R}w?KEYx@c7vfKy?ZT7<=WWE&~#Gs zYKio>QrvMgyebT!Yhh2^bupwacl}fDfrWyFFu-d_&8M$Jc?pN+;E)<)Bo8QyU7h7q z_&i~7RBuh6>M@d+@lT4!7hD`DwNs37eYGEqn92F6smp@nr2TC0b!f}Eogx*A(bIn( zICm?7CLMT}2g*;gLiE75f4!isBr#nc6|3Jsr7p$6v zoU*rw-g9yxjJ%K;N4A&F%psX?1%>dsEc7)lc36t%W*>lC zBT!f5fhb{PoYO{{R{-4RMLyIgLY9&r3`jHrn?JJC?}G;{wO$?t%ndsuzZ0rnx)*x$ zhE0QwJ!4+1bf-eLwLatQu-I=2M5?ZEuv@e^UOi^4?7g+@{#bY4{rHU&79SU?X7GN_ zq~c-6sQ20Q&PSImi?mj-U%QW-WQ{fe98`JoyD(B`IemD==3H>Ms`j?_y7G__TwCa1 z<32GK_`H*rLo)6M2~wz!HKez-ty>w#$?^~~hRwT5zO{Dg;JT%Fe2jMD_E#hs6g-0< z>~U34R6bIZ!TXBr2-$Twz}sC9hC0B^`cijjYB8y7e&MImP4^j8)O^)zT+LxPc zmFAxdxp_zA!!_k+9U^B1I;DmhlJ2V=^KUwwwx!dnT7(&om$|a($wz-O<_>TVxYRPg zJ#1wra}}%b0^x#+&vY%Z)w*}yzhm@@FR{o_!qQM%bpd#_ z3rcz-Gdm90qVb^-isTdwBW@jQk4#4rh~Qu7zYTpYww^&@WsdLXE}Q8ueRiGW$?%|| zh3hhfnWH#ucKjZlw#*hcXolT(C;he>H|^uXe&S7zae$>CB~L2Q(>m@N)eGrnO^w)k zLT}y94!2PNp+K~hD>q^!CqkooxhZ3IrITU5Ou^+s%4t-_ypX8m{)XSMSU+f7MInRu zs_Q<=Pb#t{rT1kIU>`q-Vxa4 zXXKL_^4by<*#b2X0RU)8?tq98Rq3BR?HGyD4}DcP&v-l(0^7fKppDgNSU>LRtBy)C zo-sL1igv4cMGRR|ucwjVKLMx%(2LvDUg)gArpqUg{;D;v)v5EF9ET>`UUsP9#r0s0`P*zJ+NlrWzycKa4g^Pb#K*KXsmE<3FmfO z9%ayaT!1^lD``=qwLRrzQY8}cH4)EUllxbLEov^DN)NK%pKG*>*q*eiiYOE&D7&}H-YZy$QA~RA6fet9w*o8r8r%0k53Kczkk(l(lheB zMi3i;;~*b#C0=wZA?|_doX~W0V8lLxT-l(u4xZm51&Bd2_9Yjzah2P9=0A|XqhRrl zm9?+-@`0jOmMCB$cVP|NGjQixcelA+>4`zJKeOhLZvAoO zmnwtolB0P09)I2QkPIQpF8Axh148~egXh>bpnzy=>Ei>u!`-`Z#q4zE1$C}d^jJ@C zXP}HU)niC!yyFo`#^)W2$BS>2F-+_76O~ns9N+WSDT*`7=rUn@vaxzn(EeSGmu^8; zT+)UpXb-K3_i8fHu1F?biz(l>q@BlVdy#$I@SFC|4m*H{r;LK;yo_5-r0o>#>0(Xm)n+32%6;VcN8Axu_iM>f@mL(lex(Uz*?k7RlRf}WTV0xqP`ZKuzy zQSnb0L5`)VqF8kRPI|>)mxHj}s%l0iN#?k4|JpGfpCQ z;b8>Zrcd1SF*RtxD#-EQC*WV7qgIBOxgYKmnm#wtz;GHo-v@OPvZ^;p8V;q6#E{}` z#tx3x)-sS$l1Gp8bk1Mm7%d@-g8 zyR|RgywJQ>V->&)-|`5b1KRY+A@yu+^=CXwQz^%Luc0S=D?g=T94z%j0(vH$ikx}- zFR?t-+{;Dy4xejFSgq|k>wH&LxiGKQN%o~oX`YQ!Y@MsJGy8LI?cB}62Jo4Rz#tz1 zHIl)nw?&)AZ^TKRHRYX3{%c&l&KB@|61dL@%9L6QIJ+#4v{xDj6Tp?^&_E_0mvLJe zK~ri(NOEksobhRzpZ$r+;bvSo8o8m39`YbsR9AI)cpVSKVf*O#y4-hV_h*p2F?mEb7}a!O>?ez3_r|9^w%ld~ z9uvj7mc9GdV}+vcqxU$PB8SGi*_W!Y!V%B|Jjp`&U`j}{^YeJbT&t(Ysc zd&?G8T|pXJ^)Z#}(nxs-tLm4zAV3!Qpe_Wj|sr z2b1e4e=e910um5&oMAoGJ!y}xXgmXG#URwXulK8S4suu;h;qC${)EJO;<^#|Zu4>cIXc0Z3oCbS4^Sf;hlO0`3*Dj1Z| zS>1dBxEU+~=Zw%*#&;9Z?;02x@g&(+}X$`Lhk@>|I( z&cE`DSal*zJW~GRjwhql_AbX-hVq&8c;h-^<=eUG0~jo*s7Wo_A88Tfm$B{ua6N&p zFZtk1mZGnt0 zhAcS7ZSg+d1bQD9CH!CzOng%Vd~4k9oeXS;4#(U_XS<&rYz0R&4<a>Xyq6dPUcyPVbY_8N*{m4V2LIb?3*8C1?EUs8aV#)MNFn z(?hgO@8ZYI77$R^(xAoK1SvM|9-}AF5i9B4o)Y-t`SH?(zQNC%riCv+(8`_CTO8;L zQJhl`<8}%JMz8P*&1?zT!ON9LuecyAorUyH9TXXUmpro%+3~ul;$?JrTWid!pgF(W zt6#P#{ZU3z^jsW;700OYNP7_ zw0{r>|KVa9cYe3alDBI)@@pbau>2t-6io48<O>0`z{`isk6O4x1%f&S`nZqRuI$zlT6uCHYh)I%({(+g} z;I4WwQ_L=sf!1rN1pdYSqw`YcPfZXislU0U<*~iaSU2o}zD3UDJ*4FC&(dhvuaK2I z^hj5Aj6}@6aRHe~QZQ;u+R(e9^58r`{OxL>zGejdhL#(Z@^Y&gb$JEVgL9?=hyBOI zgK1lD?)7j-ZPBrj#;3CD+rpkZeDV*z$6&l}$F{i8g zAAKtS7@Pb`hO<&)wEh5v(^M(xv3i42eUN*qrnVzg7B%}vvx$LgK>g@q9MK}YzC<=qv& zf8R46!X^Vky(U&e!!Z-Kc7P*OBU{@=RY6IeOs(nRPf~i_uflapk8+<^SGuiv`KYZ+ z5DO;#W=yRx$QyLLQ7_~~JU?%2bs_orW$w@;ylmOg39O5;5hc>f8d40{0w46%qvFHy z5I1jY^AR4ldG2HdI63AJxCDX>6zgIa!3llGEiJz+P1lA1&nns2PvDeGTE9VQe37vA zx~cLXEfX4bX(7Kyp!7dvwaI&3@;*)=AAfh}g_bj3L6layXC7+9mHaRgII?LCC^r_& zyQu=&M%mHH*c@CSw>Jp|6{~^rY~xGL9jWOxcNU+Z_(K}sEr^&VN zcXqbKg2)i!C=;E`hp0s5N+FPxKK~no4d>~)!2Zt4B$;bb)s{=>==aYdOp0nW&LxeYJ9pxvA77>`#Y(MIFDXD2ps%WV!eNvtRGs zJZB;o;rAX=@s~F{bY5P`!5N-$aw&@At#B3jWH$l)ZrvTGU+T9mR@$C!c0N`XgpNe3 z?sCW#yk5PMz=|6J_dYwK!lNqFlTeS{(V$imukK$`lPWtiPkNlNpFDr=Vu4l4=Y5dy zTOpqnSa(*n%`Q(1%@>dOtH-zueLnceb$$}1*iDfd{UF%gLvNrs0wH8~+iUpj>_!g|+u-4%E8z3DjD}ZO(Yasw#tlf{JUDy&WJvncCBvGPbY<_2b<_ zm?2qF1UWZ3v@hNvw_v>8?o;wQlTYs2S%5@CF|nvq5xJ^IOy;j8g{g@dvCVy%Ud6 zq)9JmPkiov1*s@8ktyu22RVf7(iP3Ee-h%u6wL|~Po?pUdRtU>9X(7&ffx?D;NNF& z+}MlqKVN_%LF-j?&)ZEJ17)0$=01}54XOPt^ z=zPOM;V(&sBz08v>8hGAVRdR~h={-qY8=&iZ1wzjbM1W0oX6jv>ay#Ed>F7+T=pvW zkouN1CX^@M6P*o;WFRu`@T3k$;F4ivM zhgz?X9U%;Px|w{lBOIteNoz=}QPsJnj-OtS4wVI3BJZW})8m%jKIfegMuHV51Bqu( z(u!*Y*UIuWzSavU4w#>9L)yuvJaIC*6*kR{N=d}DulNj zs{wOD6h`{I-m@7tb8;SrbEV37wM?$Xp|t@*SL8&pBUQbyt0XL|adrm?J{+QZqxh?L ze*ON5H>t`^Iw>h<&{KxjYQm~9d9E49%u*GqpTFnHM|b+(gPJE^Icg66d~1jt&{WYJ zl#U~~v~x>E40Etf;Q30K-s)w&^gVlm=*R^DjQL@a|KZWemLV3|^3qigIl1Pg;CMIZJ*7glHCseg7HLPge)nCu&9|R;pTbFG6KW@dqt5$=qk>I~Ew)%iZfcf+DMT=<2dD3=ZXUK%Fykn?9EjqFtLE@E;S z0E(K&6Z@I%5c#~dTaw6GJ4dXldadoLOfrAwo-MLMgNvit*@B!#O8_-P2f7|MZ+F`5RzxtZTpcP&4D zD&KyMfre01B11*m@y z3IV%j#7PU?n@;5z1nB_K`gl!g$;+wPH(X0K43f=(qt<9r;#KtZJrlX(Z(AOz{iLRK zR^8(JlTD?a^>2q;++@YKh&w#?CtB6i>tj^83KVRx!8`{wiN9!PjT4xqjO8J8t98LVrg^$|)-oa`^Udv5 zP8f&DG0a|IS^+QbURJpkt>ec}u3)EUUvA~+9d=Mr#Uv!`TiWZc8-uXH8kY+@GP^k0 zm~;+2%U*-0l^Pf3XE!Uc-*oK^ln|a!RybN$c{Z0{BsW(&0R{X4v zvKA;?mE|e*b|KR9EFtw2K~k+{4f|tRSZIrtEpW-gygpoB5R%4yh4JC^*dl<8pJGug2gEGd%m3=%y*?>1rETs#fH2>E!kO1N{b4 z`^)a=!|O1w_UsJ%(0Zw;R`Z|eOp3&Hi2v&xYoh{-0IlmeAu7Gjrz3y8f5&ymWJDqu~p|cwMBJ(Q{B?{nzw1DH)Pqbq}rSM zm{n9ihl8#&58OL;XlwpuxRqRUh_BbR(if%&BeH=Dzd4?tH>0~4(a_j_dgOUr(D+B| zsh>PD@&`1&*W~ZnO$F?&4;(#D6C#osWP)!YO;;ngl6FKa{jHx%+55*D9yn}Gr%bD@ z9+>6Zw|ZKm9ea*o6!}OOEAO5k4*-6?KEEl`y*An>qx29y&)?7kn=qDVobMl$G(2=kCmTZVG!J*D?2&}>RJ*p4Zq6EyIYi24HLr}fWFq^owl|*0x;9hU3E)4 zb=%3!Sj4R`By~;Ol6Yc)^AE(D8PAG^ihSPp;a@gK6$!n>xsR_3Ypspkjc)v1&IrzO zo!y#N&zr=&$m6PejivR)oNe^uH%I!+!a{D_WEL{o?yBF5* z3hYE|v*lP~DG(2e)?4l#3dlqt2|Nx1SvFXgSiC#Ww$U*E0l$X(D0y4dzRt2j zjzUYmXU9Tyy&`Z*R7`UUZR8keeZ%Z{*ut+mku8 zmnwurLJ3uVc`I<>6tkpeU%0F-0oM7Yi2uStzxIaGocUHjfWX@xl1ePj2Ypj0x za4k4YBUeQrG>1d`H|Nf=)q#qZj!c>4BmXyFyuLmj2x4Cd(K;uHA8rQy$bHcQfTno72BLNVPo~OxpRcYpb;VcVvY>FM$bmFN39FLnYU`H-7$r~_9 zqmccZgJ($Zj!zH-MvjmlsAYV3T>SDQ-J{b90P`-EW!3Mr^1=eL{>1-Evm=VZ;6q*B z6XQSF=zPiNki|MNVXfu0VRY4syyNlz${SdWGyLj{fh72&qGCTwKrwM-zkVWDqQglx zXe}!c2pXFI362@mi`Ge1?iXXo6zIdQ2&i;18iHkz;b!v2TYQX5oL1t8#gqle+NnE?_O?{+I9oIFT-_0 zeSx6Y5tL~X!$U)*MAS&2prDYP?JHXrf1=C3P=I{nX3p+AHm7W-Bxg8PkOuVQZa7i! zVWg|yY;54|Q?#TYa={n$jN2HE9Jxq>b5fRnNkm16Y|RIj6hOz1l`nQqZf&u+ciD?E zm4bYmbt`g9YioyIaW0>!UJ3=ra(TW(1Z)cZ83|qaZ7;!mCoU=Ja4?z(@YbgGft(Ov z$)Fu-@%LQNPXruy)oy69@MNHfs>xZ>{rNF^m^rj;1K zxzN_NAcCR=fqv(zl(aTPf^xCxpbt3>g+0YPEr6hT5gXkSiXTyik+lJ(2aTst60+~# zaO4zlpnAZ zs$=?;hEgmI^(cApCyT9d(FQqp_4th`5s(?*nKPuG(K|~ijoZ4UP{{gG&`?mQDzJ3x z8&cc&OJn!*v8n#+07}mvivLvumIwora5Cb2Wn8a0rTXfsM?s-0hS7l;Ly8+Cz?>?f zOUr~xqvdIJvj(fvhH11h0*Nv(8tyBy=||o`{}3evN_Na}ww{kuj4FBHainrSTcD(v zK1+jARhg)|`cXX}nV7hmBb%R}@chz(vQ7oT--PfTVt_U=-X#L$c0V-w5aVz?y{^(96~3L%AP17r8@^?IUJSS}L9 zi}at8dfCBf7#YdSNiu>#H%A#Y$3HLK(}Q1LdK3-%}1v`aoUL9aKi|V5-gMgXts&Rg=F52d6wUZrZYQBxeI{DC)#|?8y2C)ZchW|rY|*mf&~$s4a9t6{t<}D@Q|(vY`8GDGfS}R zgXR{C{-2r+H@5%O{W&^A8sfgrV^o6AD`!^7xYvI8C~hw^Kc93MR(K)(C{dkk5sM1ypR5`5E%c+Pro1mJy@8I~CM0Llw~I_Q97nFf~A zW82xm{Z$;z$<578)@d1idJ@znVRXkGOWGKpZ(F+2f5z1v-5Oy8uC5H#;b?C{fWDMy zJ4EeczULp8P5=MLBCE%O{|V!g}nrcdV*XJWl0qVE0~h(H3X!rI27 z12bIY%8rm!3;H0RwcbhyskUnq3P*h6FOf7c;e@pM>rf^O6ar z=1p&Qt83i%^%ZAafp_z-V%j`&Xdr;;Ho&;a1%dN&^>hy#_TIZY(1XrR-8J;)N#n0L z7^3O&`+5G`!FdJW$B~B=?K9EG$xLRqz4!ehvUHhy_K!1N;i}e<$f~Vyi!IHM`vZ*2 zHAluOC+v%g;cu&;B_gC`Q@2#)A^-~DA* zf)~5pJ92gMhNlrM=B*9#@vt`2<`wk$>`~}t|ILzWW-$wLHy1lUc8xwOCjHKo`Z8-j z7re4Ak-l>8Bq)-HHXQ*kda@L}vMl5z4(&8y<>=^DRxyar6F0p`!+^;PQ{YFcDmCN# zV1Vk>XM4E|>Ryxaj030VRMn#3eP8y(1}n?;&1q(MZAJcijioMO#;QFTlyvrZ?hV719cxE5(TN*F)kq32U6;X9vs(R1wim;~lKooT_(cqx$pC zAJ7z|pd0si#-8PbBmilUK;-I%JJ2%E;xl95U48D1aY-b}Pmx`s35HvA}8URtv3 z)y7{3NZ0xK18>?nU7ykEubU3^f#)egi%*YkC#jG0OV)?>m#>T7x-X?mMN2jq;gv-# zTGh6wU$N+KT0OTUiE4bdB;QlZ!Id?X6)d&+EY2JREBTxf&yai4)X*dwmAp>q$mzX{ z4dTLN6}mku1HS;eG4M|%HD;(sCwxUmlmjCus7;_IlBbZz{#g-odu9<=#NFIZVi0^S zH+e1gpWM)Wyey5DXn9@D>MD_^UUO&fT zSbIe26rEC0`0>g?pL>6J!8fF?zaR4(c>;B<*FLD)D$>o_=q+{X{E-2^z>l^yfGW}I zxK~{O)%pj5mSU0ZR@Zw>+tT{fbwd@(`?RLDEOPE!H)f+Z5;v_nb5*kG;h**mDgJ6; z03ai7Gaj_hxgt3@D0l-_HvOaZU&<|;4x+%7y2c4G*dy0|PKbyS3}k2C)vDk~X046H zRz2L#CLj2Q56^cJxf5q_8^h@yGHRdSxqAgnQg7Ed1v#+!#2Oo{hKJV*6bS1*aq4cm z#F6qoI#hx~^syKFA~Twi(D{vo#V{*C4v3ot7)YgBJ@Z!Qg1*5Be!gGu=^7BNh^6dk z^mT~V7nSDwopDA*&AfzGnu5yH4^*f1@pfv|6!@mwF|5slVfB#Z_kAFKmf~#O<1Vej zlESxzqPe!h2jFX{7m*v1ePuEjtmV`PhQpy6iK)Uj&5(W$r#G+bH;rhWgI31ClxrNh z6tm|rW$=;92yT+rQFgxCoFgP24@ah%eUgF;vj*#armL2xdV4|CVcwPz}!5o zFmEXVIZr^#Rkw(n-pnMgentH(tCfQQ%hlTKUQkcmv{8nL(LO2J-d$l424sRJVsm9- zR7?^viDyJX&{536Z?BMNKTB|wX=HFEc-gJZ9u+W6s;#D=_tm zaZ%Uv;NGoDqw>QnrRJq)OJmN2UUM^PmB$S<%6^Gt^2$M@TVQZWx2YaxZig|$2q#%L z7oxFVXLn*~h*Vab!~-^BVcg?0f)IP^nztw8EyWOe3Bq?KShK4*-xX>ze<7{iia+Uu z9di?>xegbrZQLvw^wV~hsI6HLIe@kl1tw@IUstkkOOew5gm(I|fhP$pGgM<6 zCue^q#I7iK>J}bV{TZKF@Beg+87|;~J}lqrQL(Au)H5rA zMvKD)13FWF69uWG3vkRTe8}80ez01(G~Cb;BZGM=c|rMz)16e}cvM5)VhO6;8C=+B z7!=t0G@hwf6Dyqi-*6EM*wVKJWe8i1#9y_D=cjYpDlg+6#ml_3G-J>_fA+~e%dM|o+@4e#+Wv1tPG7{!;5Jm)h$ZR|T~crGg3LNweug0^3*n$8%yz*`KUV zy+&x2_=giJRD61q8(ZlT!Fs43n@!~fa6iBs>W@jt0PGrez85B)3ekkqf9WU<*S+Bx zf4y#-(wXi-x^MD9XwcSfUvDYT*IBk5YHE2nYdL>*p7B0`%Yr3vQQ*Xni0SJSgD^A5 zGX1*zZ4!_FUR^5;`cv79>fg3AKWwA{u4P47??1@)qoazQ;THAO2OvHt`=--jN0@fh#;4g`G~kk0hbj46|$)>DF`QC~!Sy~!ljIoCHd zRB6m)Py!;}+T_FahbiylHZjc9`qcah=j}42<|dy?5RD!9Ejse;dD43Zwcfz|T?^lX z5UNd9g#L=4LVl4XHWHr`a+mJETHK3O`;> zD%8T)pP0#vQ3(_)oP#=2+1n~5y;8-wB)sr)-a)HUIylgJE^d)fQi{#Nd^8uW;4>p25u z;{EvBoBaSKJ!@@hrT)NZ9po>9Xv(FM2Qy_D;(EXG=@Aup5720xzIm3EB?xMx8(Mom zFC%+^!; z$3WYx-Qyk6C3#{D0)mMWn^tR|Q=n)4L#i#BleY3rX zFPprluMRHH;kiF6b6<_ys{UcdLYn@8R~ng!eizFJWpy~H$10W##UNGN~`&jFOO3oN~$diLP5-O_iKE@BB6l7 zR?V*q0h#d3+G>l9PAdZyyl1e?7$cx(|6;Sy-~Zu@#h+uvioH6ZiBwUsab`{OO!Y-Z zaV)p1%X1?9gY-c0sWdC$;^R9I34}T*ywFMg`Ny9qK+*S$q_^hp!=(hm{pRNEu`!Cz z-Ni{-rT;}E)c->xt*x36kdOzLnO}W;1VM-3;B83o+zUuUgDma+3D}{tvwY zahQ&O3X=#yHwY~$Er${!KoxeVLsgZP9cm};3+w748b$3X77=N7Zzv?yYn8oGz6mOj zL6qY^1o-QJ>=Kr_{GYYp-+yEO{2!(iL7@G=Ob?bM_}l+@BuZcS|CjSWefMv&|NpZ6 Z5ae$!OIMH=Bme{bNsB9pRf_2Q{~z65v{?WE diff --git a/docs/template-registry.md b/docs/template-registry.md index 2266957..6036e86 100644 --- a/docs/template-registry.md +++ b/docs/template-registry.md @@ -2,17 +2,138 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) -## Template Registry +## The Template Registry -The TTemplateRegistry utility class is the easiest way to maintain multiple templates in a project, spanning file and application resources. +Most applications all have the same requirements when it comes to managing templates. The TTemplateRegistry utility class is a productivity booster and is the easiest way to maintain multiple templates in a project, spanning file and application resources. + +The default behavour of parsing and evaluating templates is managed using the IContext, ITemplate interfaces and the Template utility class. Management of templates is then left to the developer. + +The TTemplateRegistry wraps the management of templates into a consistent way which can be integrated into any application / framework easily, providing the developer with maximum configurability. + +### Example Usage + +To illustrate the most basic usage using the Horse web framework. +``` +program DemoServer; +{$R *.res} +uses + Sempare.Template, + Horse; + +begin + THorse.Get('/', + procedure(Req: THorseRequest; Res: THorseResponse) + begin + Res.Send(TSempareServerPages.Instance.Eval('index')); + end); + THorse.All('/*', + procedure(Req: THorseRequest; Res: THorseResponse) + begin + Res.Send(TSempareServerPages.Instance.Eval('error404')); + end); + THorse.Listen(8080); +end; +``` + +Thats it. The only thing that is required otherwise is a templates directory containing index.tpl and error404.tpl. + +e.g. index.tpl: +``` +

    Hello World

    +

    The date is <% fmtdt('yyyy-mm-dd',DtNow()) %>

    +``` + + +e.g. error404.tpl: +``` +

    Page not found

    +

    Please try another url...

    +``` + + +e.g. DemoServer.rc: +``` +index_tpl RCDATA "templates\\index.tpl" +error404_tpl RCDATA "templates\\error404.tpl" +``` + +NOTE: Sempare.Template.RCGenerator can be used to automatically generate the DemoServer.rc so you don't have to. + + +The framework is also aware of the normal IDE structure where the application is installed in the directory [platform]/[configuration]. While debugging, it will first check for templates relative to the application, and otherwise search ..\\..\\templates. + + +### TSempareServerPages interface + +TSempareServerPages is actually an alias for TTemplateRegistry. The following public methods and properties: +``` + TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadCustom); + TTemplateNameContextResolver = reference to function: TArray; + TTemplateNameResolver = reference to function(const AName: string; const AContext: TArray): string; + TTemplateResolver = reference to function(const AContext: ITemplateContext; const AName: string): ITemplate; + TTempateLogException = reference to procedure(const AException: Exception); + + TTemplateRegistry = class + + // Resolve a template by name + function GetTemplate(const ATemplateName: string): ITemplate; overload; + + // Evaluate a ATemplate with output going to a AOutputStream + procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate); overload; + procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate; const AData: T); overload; + + // Evaluate a ATemplate with output going to a string + function Eval(const ATemplate: ITemplate; const AData: T): string; overload; + function Eval(const ATemplate: ITemplate): string; overload; + + // Evaluate a template given a ATemplateName with output going to a AOutputStream + procedure Eval(const AOutputStream: TStream; const ATemplateName: string); overload; + procedure Eval(const AOutputStream: TStream; const ATemplateName: string; const AData: T); overload; + + // Evaluate a ATemplateName with output going to a string + function Eval(const ATemplateName: string; const AData: T): string; overload; + function Eval(const ATemplateName: string): string; overload; + + // Provides access to the singleton instance of the registry + class property Instance: TTemplateRegistry; // read only + + // Provides access to the context used by parsing and evaluating templates + property Context: ITemplateContext; // read only + + // Allow a custom name resolver to map onto application resources + property ResourceNameResolver: TTemplateNameResolver; // read/write + + // Allow a custom name resolver to map onto file resources + property FileNameResolver: TTemplateNameResolver; // read/write + + // The folder where templates will be accessed when using tlsLoadFile + + property TemplateRootFolder: string; // read/write + + // The extenstion for templates. Defaults to .tpl + property TemplateFileExt: string; // read/write + + // The load strategy order. Defaults to [tlsResource] in RELEASE, and [tlsFile, tlsResource] in DEBUG + property LoadStrategy: TArray; // read/write + + // The template refresh interval when AutomaticRefresh is true when tlsFile in LoadStrategy + property RefreshIntervalS: integer; // read/write + + // When true, will refresh templates in the background. + property AutomaticRefresh: boolean; // read/write + + // As Context.TemplateResolver is used by the TTemplateRegistry, this provides for custom loading. + property CustomTemplateLoader: TTemplateResolver; // read/write + + // Allows for additional contextual information to be provided to the name resolvers + property NameContextResolver: TTemplateNameContextResolver; // read/write + + // Allow for custom logging when there are exceptions loading or parsing templates + property ExceptionLogger: TTempateLogException; // read/write + end; + +``` -TODO: -- describe default behaviour -- customisation - - naming overrides - - live changes using auto refresh - - loading strategies (file, resource, file else resource) - - context ## Resource File Creation Tool diff --git a/docs/tricks.md b/docs/tricks.md index b5adced..2f1b09c 100644 --- a/docs/tricks.md +++ b/docs/tricks.md @@ -4,8 +4,44 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ## Tricks -### Reference variables dynamically +- [Functional Content](#functional-content) +- [Dynamic variable references](#dynamic-variable-resolution) +- [Loading templates dynamically](#dynamic-loading-templates) +- [Empty Value Evaluation](#empty-value-evaluation) +

    Functional content within templates

    + + +The template engine does not allow for functions to be defined within templates. Developers can expose functions to the scripting environment using [custom functions](./custom-functions.md). + +However, what may not necessarily be evident is that the 'include' and 'extends' statements can take an extra parameter to customise behaviour. + +The template may be defined as: +``` +<% template ('template' %><% _ %><% end %> +``` + +The _ expression evaluates to the value passed to the template. + +Using includes: +``` +<% include ('template', 'hello') %> <% include ('template', 'world') %> +``` + +Using extends: +``` +<% extends ('template', 'hello') %><% end %> <% extends ('template', 'world') %><% end %> +``` + +The above examples should output +``` +hello world +``` + +The above illustrates some powerful capabilities where the template could actually do anything such as looping through structures and dereferencing very specific content. + +

    Reference variables dynamically

    + You can dereference variables dyanmically using the array dereferencing syntax: ``` type @@ -30,11 +66,8 @@ The above example will produce: ``` abcd ``` -### Improving performance on larger templates -A quick win would be to use buffered stream so that characters in the buffer are processed quickly. - -### Loading templates dynamically +

    Loading templates dynamically

    If you reference templates using the 'include' statement, you can rely on a TemplateResolver to load them if they are not defined within the template itself. @@ -45,37 +78,88 @@ e.g. This is illustrative: ctx := Template.Context; ctx.TemplateResolver := function(AContext: ITemplateContext; const ATemplate: string): ITemplate var - LStream : TFileStream; + LStream : TFileStream; begin LStream := TFileStream.Create(ATemplate + '.tpl', fmOpenRead); exit(Template.Parse(AContext, LStream)); end; ``` -### Including values when variables are defined +Also see the section on the [Template Registry](./template-registry.md). + +

    Empty Value Evaluation

    When a variable has a non default value, it can be used in condition statements: ``` <% a := false %> <% if a %> - value is set + value is set <% end %> <% a := 'some value' %> <% if a %> - value is set + value is set <% end %> ``` -If you have passed an object, the boolean check will essentially check that the value is not null: +If you have passed an object, the boolean check will essentially check that the value is not nil: ``` var x := TPerson.Create; ``` The template could check as follows: ``` <% if x %> - <% x.name %> + <% x.name %> +<% end %> + +``` + +This logic also applies to arrays, collections and TDataSet. + +You could do the following: +``` +<% if collection.count > 0 %> + <% collection.count %> +<% end %> + +``` + +You could simply do the following. It is useful knowing that the collection is not nil, but you probably are going to only work with the collection if it actually has values! +``` +<% if collection %> + <% collection.count %> +<% end %> + +``` + +Similarly for TDataSet. + +``` +<% if dataset.recordcount > 0 %> + <% dataset.recordcount %> +<% else %> + There are no records <% end %> ``` + +You could simply do the following. It is useful knowing that the collection is not nil, but you probably are going to only work with the collection if it actually has values! +``` +<% if dataset %> + <% for record in dataset %> + <% record.name %> + <% end %> +<% else %> + There are no records +<% end %> +``` + +However, you could simplify the above to the following: +``` +<% for record in dataset %> + <% record.name %> + <% onempty %> + There are no records +<% end %> +``` From 22c3abb06d6f8cc02780cb770a998e1ef80d857b Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 15:33:39 +0100 Subject: [PATCH 066/138] Add payment links --- README.md | 20 +++++++++++++++----- docs/commercial.license.md | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 095149c..bebf9c6 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ Template engines are used often in technology where text needs to be customised - web sites using template engines (for server side scripting) - code generation - mail merge -- notification messages +- notification messages + +Please review the [License](#License) section below before including the Sempare Template Engine in commercial products. The Sempare Template Engine is a small templating engine for [Delphi](https://www.embarcadero.com/products/delphi) (Object Pascal) that allows for templates to be created easily and efficiently by providing a simple and easy to use API. @@ -216,11 +218,19 @@ no cost to you, or you may license it for use under the [Sempare Limited Commerc The dual-licensing scheme allows you to use and test the library with no restrictions, but subject to the terms of the GPL. A nominal fee is requested to support the maintenance of the library if the product is to be used in commercial products. This support fee binds you to the commercial license, removing any of the GPL restrictions, and allowing you to use the library in your products as you will. The Sempare Template Engine may NOT be included or distributed as part of another commercial library or framework without approval / commercial review. A commercial licence grants you the right to use Sempare Template Engine in your own applications, royalty free, and without any requirement to disclose your source code nor any modifications to -Sempare Templte Engine or to any other party. A commercial licence lasts into perpetuity, and entitles you to all future updates. +Sempare Template Engine or to any other party. A commercial license lasts into perpetuity, and entitles you to all future updates. + +A commercial licence is provided per developer developing applications that uses the Sempare Template Engine. The initial license fee is $70 per developer. +For support thereafter, at your discretion, a support fee of $30 per developer per year would be appreciated. The initial site licensing fee is $1,500 and the annual site support licensing fee is $500. + +The following payment links allow you to quickly subscribe. Please note that the initial license and support links are seperate. +- [Initial License Fee](https://buy.stripe.com/aEU7t61N88pffQIdQQ) +- [Annual Support License Fee](https://buy.stripe.com/00g8xa4ZkbBr480145) -A commercial licence is provided per developer developing applications that uses the Sempare Template Engine. The initial license fee is $70 per developer and includes the first year of support. -For support thereafter, at your discretion, a support fee of $30 per developer per year would be appreciated. Please contact us for site license pricing. +The following payment links are available for site licenses. Please note that the initial license and support links are seperate. +- [Initial Site License Fee](https://buy.stripe.com/eVa00E77s7lbfQIaEG) +- [Annual Support License Fee](https://buy.stripe.com/6oE14I4Zk6h7fQI003) -Please send an e-mail to info@sempare.ltd to request an invoice which will contain the bank details. +Please send an e-mail to info@sempare.ltd to request an invoice which will contain alternative payment details. Support and enhancement requests submitted by users that pay for support will be prioritised. New developments may incur additional costs depending on time required for implementation. diff --git a/docs/commercial.license.md b/docs/commercial.license.md index 52c3c2d..d8367b7 100644 --- a/docs/commercial.license.md +++ b/docs/commercial.license.md @@ -8,11 +8,13 @@ The commercial license for Sempare Template Engine for Delphi gives you the righ - use the component and source code on all development systems used by the developer - sell any number of applications in any quantity without any additional run-time fees required +If you have obtained a site license, the license applies to all users in your organisation. + The Sempare Template Engine may NOT be included or distributed as part of another commercial library or framework without approval / commercial review. A commercial licence is licensed per developer developing applications that use Sempare Template Engine for Delphi. The initial license fee is $70 per developer and includes first year of support. For support thereafter, at your discretion, -a support fee of $30 per developer per year would be appreciated. Please contact us for site license pricing. +a support fee of $30 per developer per year would be appreciated. The initial site licensing fee is $1,500 and the annual site support licensing fee is $500. Please send an e-mail to info@sempare.ltd to request an invoice which will contain the bank details. @@ -27,5 +29,17 @@ USE THIS SOFTWARE, EVEN IF SEMPARE LIMITED HAS BEEN ADVISED OF THE POSSIBILITY O TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL SEMPARE LIMITED BE LIABLE FOR ANY CONSEQUENTIAL, INCIDENTAL, DIRECT, INDIRECT, SPECIAL, PUNITIVE, OR OTHER DAMAGES WHATSOEVER, INCLUDING BUT NOT LIMITED TO DAMAGES OR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER -PECUNIARY LOSS, EVEN IF SEMPARE LIMITED HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +PECUNIARY LOSS, EVEN IF SEMPARE LIMITED HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +# Stripe Payment links + +The following payment links allow you to quickly subscribe. Please note that the initial license and support links are seperate. +- [Initial License Fee](https://buy.stripe.com/aEU7t61N88pffQIdQQ) +- [Annual Support License Fee](https://buy.stripe.com/00g8xa4ZkbBr480145) + +The following payment links are available for site licenses. Please note that the initial license and support links are seperate. +- [Initial Site License Fee](https://buy.stripe.com/eVa00E77s7lbfQIaEG) +- [Annual Support License Fee](https://buy.stripe.com/6oE14I4Zk6h7fQI003) + +Please send an e-mail to info@sempare.ltd to request an invoice which will contain alternative payment details. From 86cfe87c44720614a5754ef5c93690449f1ed085 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 15:50:32 +0100 Subject: [PATCH 067/138] Update navigation --- docs/statements.md | 57 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/docs/statements.md b/docs/statements.md index 445e266..e8d1f97 100644 --- a/docs/statements.md +++ b/docs/statements.md @@ -4,21 +4,21 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ## Statements -1. [print](#print) -2. [assignment](#assignment) -3. [if](#if) -4. [for](#for) -5. [while](#while) -6. [include](#include) -7. [block](#block) -8. [extends](#extends) -9. [with](#with) -10. [template](#template) -11. [require](#require) -12. [ignorenl](#ignorenl) -13. [cycle](#cycle) - -### print +- [print](#print) +- [assignment](#assignment) +- [if](#if) +- [for](#for) +- [while](#while) +- [include](#include) +- [block](#block) +- [extends](#extends) +- [with](#with) +- [template](#template) +- [require](#require) +- [ignorenl](#ignorenl) +- [cycle](#cycle) + +

    print

    ![print expr](./images/stmt_print_expr.svg) @@ -44,7 +44,7 @@ You may also rely on the print() statement. <% print('this is a test') %> ``` -### assignment +

    assignment

    ![assign](./images/stmt_assign.svg) @@ -54,7 +54,7 @@ Within a script block, you can create temporary variables for use within the tem <% str := 'abc' %> <% bool := false %> ``` -### if +

    if

    ![if](./images/stmt_if.svg) @@ -90,7 +90,7 @@ the list as at least one item <% end %> ``` -### for +

    for

    ![for range](./images/stmt_for_range.svg) @@ -212,8 +212,7 @@ end; Using for-in on TDataSet, the loop variable enumerates rows. The loop variable can be dereferenced with the name of the field in the given row. - -### while +

    while

    ![while](./images/stmt_while.svg) @@ -262,7 +261,7 @@ While loops may also have _onbegin_, _onend_, _betweenitems_ and _onempty_ event <% end %> ``` -## break / continue +

    break / continue

    The _for_ and _while_ loops can include the following _break_ and _continue_ statements to assist with flow control. @@ -290,7 +289,7 @@ This will produce 0 1 2 ``` -### include +

    include

    ![include](./images/stmt_include.svg) @@ -315,7 +314,7 @@ begin include() can also take a second parameter, allowing for improved scoping of variables, similar to the _with_ statement. -### block +

    block

    ![block](./images/stmt_block.svg) @@ -329,7 +328,7 @@ Some content The block statment defines a placeholder in a template that can be replaced. When a block is contained within an extends block, it will be a replacement in the referenced template. -### extends +

    extends

    ![extends](./images/stmt_extends.svg) @@ -384,7 +383,7 @@ would resolve to: extends() can also take a second parameter, allowing for improved scoping of variables, similar to the _with_ statement. -### with +

    with

    ![with](./images/stmt_with.svg) @@ -418,7 +417,7 @@ begin The _with()_ statement will push all fields/properties in a record/class into a new scope/stack frame. -### template +

    template

    ![template](./images/stmt_template.svg) @@ -435,7 +434,7 @@ Using the TInfo structure above it could be appli <% include ('mytemplate', level1.level2.level3.level4) %> ``` -### require +

    require

    ![require](./images/stmt_require.svg) @@ -459,7 +458,7 @@ An exeption is thrown when the _require_ can take multiple parameters - in which case the input must match one of the types listed. -### ignorenl +

    ignorenl

    ![ignorenl](./images/stmt_ignorenl.svg) @@ -488,7 +487,7 @@ This would yield something like The _eoAllowIgnoreNL_ must be provided in the Context.Options or via Template.Eval() options. -### cycle +

    cycle

    ![cycle](./images/stmt_cycle.svg) From a4be39db16b66656c31cfcbd73fa67a397794bcc Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:14:56 +0100 Subject: [PATCH 068/138] Handle exceptions if possible for the PrettyPrint panel --- demo/VelocityDemo/Sempare.Template.DemoForm.pas | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.pas b/demo/VelocityDemo/Sempare.Template.DemoForm.pas index 863a92d..27908c9 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.pas +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.pas @@ -399,19 +399,23 @@ procedure TFormRealTime.OnException(Sender: TObject; E: Exception); end; procedure TFormRealTime.Process; +var + LPrettyOk: boolean; begin if not Finit then exit; GridPropsToContext; + LPrettyOk := false; try - // evalEngine.Enabled := true; - memoOutput.Lines.Text := Template.Eval(FContext, FTemplate); memoPrettyPrint.Lines.Text := Sempare.Template.Template.PrettyPrint(FTemplate); + LPrettyOk := true; + memoOutput.Lines.Text := Template.Eval(FContext, FTemplate); except on E: Exception do begin memoOutput.Lines.Text := E.Message; - memoPrettyPrint.Lines.Text := ''; + if not LPrettyOk then + memoPrettyPrint.Lines.Text := ''; end; end; WriteTmpHtml; From b1ddd2529081ae8dac2b69053f752d7ac2ac30dc Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:15:49 +0100 Subject: [PATCH 069/138] Add local variable to ease debugging --- src/Sempare.Template.Evaluate.pas | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 1848669..3d05f62 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -970,11 +970,16 @@ function TEvaluationTemplateVisitor.Invoke(const AFuncCall: IFunctionCallExpr; c LMethod: TRttiMethod; function GetParamCount: integer; + var + LParams: TArray; + LParam: TRttiParameter; begin - result := length(LMethod.GetParameters); + LParams := LMethod.GetParameters; + result := length(LParams); if result > 0 then begin - if LMethod.GetParameters[0].ParamType.Handle = TypeInfo(ITemplateContext) then + LParam := LParams[0]; + if LParam.ParamType.Handle = TypeInfo(ITemplateContext) then dec(result); end; end; From d804d029061a3d608f139a00e8b5257f80f85849 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:16:16 +0100 Subject: [PATCH 070/138] Add helper methods --- src/Sempare.Template.Functions.pas | 41 +++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Sempare.Template.Functions.pas b/src/Sempare.Template.Functions.pas index 3855982..7aa501b 100644 --- a/src/Sempare.Template.Functions.pas +++ b/src/Sempare.Template.Functions.pas @@ -170,9 +170,13 @@ TInternalFuntions = class class function UCFirst(const AString: string): string; static; class function Rev(const AString: string): string; static; class function IsNull(const AValue: TValue): boolean; static; + class function IsNil(const AValue: TValue): boolean; static; + class function IsEmpty(const AValue: TValue): boolean; static; + class function IsObject(const AValue: TValue): boolean; static; + class function IsRecord(const AValue: TValue): boolean; static; class function IsStr(const AValue: TValue): boolean; static; class function IsInt(const AValue: TValue): boolean; static; - class function IsBool(const AValue): boolean; static; + class function IsBool(const AValue: TValue): boolean; static; class function IsNum(const AValue: TValue): boolean; static; class function StartsWith(const AString, ASearch: string): boolean; overload; static; class function StartsWith(const AString, ASearch: string; const AIgnoreCase: boolean): boolean; overload; static; @@ -197,6 +201,7 @@ TInternalFuntions = class class function Abs(const AValue: double): double; static; {$IFDEF SEMPARE_TEMPLATE_FIREDAC} class function RecordCount(const ADataset: TDataSet): integer; static; + class function IsDataSet(const ADataset: TValue): boolean; static; {$ENDIF} {$IFDEF SUPPORT_ENCODING} class function Base64Encode(const AStr: string): string; static; @@ -698,6 +703,11 @@ class function TInternalFuntions.Rev(const AString: string): string; exit(reverse(AString)); end; +class function TInternalFuntions.IsNil(const AValue: TValue): boolean; +begin + exit(IsNull(AValue)); +end; + class function TInternalFuntions.IsNull(const AValue: TValue): boolean; begin exit(Sempare.Template.Rtti.IsNull(AValue)); @@ -713,9 +723,19 @@ class function TInternalFuntions.IsInt(const AValue: TValue): boolean; exit(isIntLike(AValue)); end; -class function TInternalFuntions.IsBool(const AValue): boolean; +class function TInternalFuntions.IsBool(const AValue: TValue): boolean; +begin + exit(Sempare.Template.Rtti.IsBool(AValue)); +end; + +class function TInternalFuntions.IsEmpty(const AValue: TValue): boolean; +var + LObject: TObject; begin - exit(IsBool(AValue)); + if not AValue.IsObject then + exit(false); + LObject := AValue.AsObject; + exit(not assigned(LObject) or IsEmptyObject(LObject)); end; class function TInternalFuntions.IsNum(const AValue: TValue): boolean; @@ -723,6 +743,16 @@ class function TInternalFuntions.IsNum(const AValue: TValue): boolean; exit(isnumlike(AValue)); end; +class function TInternalFuntions.IsObject(const AValue: TValue): boolean; +begin + exit(AValue.IsObject); +end; + +class function TInternalFuntions.IsRecord(const AValue: TValue): boolean; +begin + exit(AValue.Kind in [tkRecord, tkMRecord]); +end; + constructor TTemplateFunctions.Create; begin FFunctions := TDictionary < string, TArray < TRttiMethod >>.Create; @@ -778,6 +808,11 @@ class function TInternalFuntions.PadLeft(const AStr: string; const ANum: integer end; {$IFDEF SEMPARE_TEMPLATE_FIREDAC} +class function TInternalFuntions.IsDataSet(const ADataset: TValue): boolean; +begin + exit(ADataset.IsObject and ADataset.IsInstanceOf(TDataSet)); +end; + class function TInternalFuntions.RecordCount(const ADataset: TDataSet): integer; begin exit(ADataset.RecordCount); From 7a73a094583f5edaebd739715e2dac2a94624663 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:17:35 +0100 Subject: [PATCH 071/138] Fix AsBoolean when given a string --- src/Sempare.Template.Rtti.pas | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Sempare.Template.Rtti.pas b/src/Sempare.Template.Rtti.pas index 76ffab9..684b001 100644 --- a/src/Sempare.Template.Rtti.pas +++ b/src/Sempare.Template.Rtti.pas @@ -278,6 +278,8 @@ function IsStrLike(const AValue: TValue): boolean; function IsIntLike(const AValue: TValue): boolean; begin + if AValue.Kind = tkFloat then + exit(abs(AValue.asExtended - trunc(AValue.asExtended)) < 1E-8); exit(AValue.Kind in INT_LIKE); end; @@ -290,7 +292,7 @@ function AsNum(const AValue: TValue; const AContext: ITemplateContext): extended begin case AValue.Kind of tkFloat: - exit(AValue.AsExtended); + exit(AValue.asExtended); tkString, tkWString, tkUString, tkLString: exit(StrToFloat(AValue.AsString, AContext.FormatSettings)); tkInteger, tkInt64: @@ -357,9 +359,9 @@ function AsBoolean(const AValue: TValue): boolean; tkInteger, tkInt64: exit(AValue.AsInt64 <> 0); tkFloat: - exit(AValue.AsExtended <> 0); + exit(AValue.asExtended <> 0); tkString, tkWString, tkUString, tkLString: - exit(AValue.AsString <> ''); + exit(AValue.AsString.ToLower = 'true'); tkDynArray: exit(AValue.GetArrayLength > 0); else @@ -409,9 +411,9 @@ function AsString(const AValue: TValue; const AContext: ITemplateContext): strin exit(inttostr(AValue.AsInt64)); tkFloat: if AValue.TypeInfo = TypeInfo(TDateTime) then - exit(DateTimeToStr(DoubleToDT(AValue.AsExtended), AContext.FormatSettings)) + exit(DateTimeToStr(DoubleToDT(AValue.asExtended), AContext.FormatSettings)) else - exit(FloatToStr(AValue.AsExtended, AContext.FormatSettings)); + exit(FloatToStr(AValue.asExtended, AContext.FormatSettings)); tkString, tkWString, tkUString, tkLString: exit(AValue.AsString); tkDynArray, tkArray: @@ -439,7 +441,7 @@ function AsDateTime(const AValue: TValue): TDateTime; tkInteger, tkInt64: exit(DoubleToDT(AValue.AsInt64)); tkFloat: - exit(DoubleToDT(AValue.AsExtended)); + exit(DoubleToDT(AValue.asExtended)); tkString, tkWString, tkUString, tkLString: exit(StrToDateTime(AValue.AsString)); else From b154bfc12d79769f48e3eff28d3bf46c2046afe9 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:18:09 +0100 Subject: [PATCH 072/138] Improve navigation and formatting --- docs/builtin-functions.md | 493 +++++++++++++++++++++++++++----------- 1 file changed, 356 insertions(+), 137 deletions(-) diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md index 0aec83b..6b3a727 100644 --- a/docs/builtin-functions.md +++ b/docs/builtin-functions.md @@ -4,238 +4,457 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ## Builtin functions -- [trim](#trim) -- [substr](#substr) -- [substring](#substring) -- [pos](#pos) -- [dtnow](#dtnow) -- [fmtdt](#fmtdt) -- [fmt](#fmt) -- [len](#len) -- [str](#str) -- [isstr](#isstr) -- [int](#int) -- [isint](#isint) -- [num](#num) -- [isnum](#isnum) -- [isnull](#isnull) -- [split](#split) -- [rev](#rev) -- [ucfirst](#ucfirst) -- [uppercase](#uppercase) -- [lowercase](#lowercase) -- [startswith](#startswith) -- [endswith](#endswith) -- [typeof](#typeof) -- [replace](#replace) -- [match](#match) -- [sort](#sort) -- [chr](#chr) -- [ord](#ord) -- [padleft](#padleft) -- [padright](#padright) -- [tabs](#tabs) -- [spaces](#spaces) -- [crnl](#crnl) -- [nl](#nl) -- [recordcount](#recordcount) -- [min](#min) -- [max](#max) -- [abs](#abs) - -## trim(string) -Remove whitespace from a string. - -```<% trim(' hello ') %>``` -## substr(string, start[, length]) -Return a substring starting at 'start'. If 'start' is less than 0, it will be the offset from the end of the string. +- char functions + - [chr](#chr) + - [ord](#ord) + +- date functions + - [dtnow](#dtnow) + - [fmtdt](#fmtdt) + +- dataset functions + - [isdataset](#isdataset) + - [recordcount](#recordcount) + +- encoding/decoding functions + - [base64decode](#base64decode) + - [base64encode](#base64encode) + - [formdecode](#formdecode) + - [htmlescape](#htmlescape) + - [htmlunescape](#htmlunescape) + - [urldecode](#urldecode) + +- hashing functions + - [md5](#md5) + - [sha1](#sha1) + - [sha256](#sha256) + +- misc functions + - [isempty](#isempty) + - [sort](#sort) + - [templateexists](#templateexists) + +- numeric functions + - [abs](#abs) + - [min](#min) + - [max](#max) + +- string functions + - [crnl](#crnl) + - [endswith](#endswith) + - [fmt](#fmt) + - [len](#len) + - [lowercase](#lowercase) + - [match](#match) + - [nl](#nl) + - [padleft](#padleft) + - [padright](#padright) + - [pos](#pos) + - [replace](#replace) + - [rev](#rev) + - [spaces](#spaces) + - [split](#split) + - [startswith](#startswith) + - [substr](#substr) + - [substring](#substring) + - [tabs](#tabs) + - [trim](#trim) + - [ucfirst](#ucfirst) + - [uppercase](#uppercase) + +- type functions + - conversion functions + - [bool](#bool) + - [int](#int) + - [num](#num) + - [str](#str) + + - type checking functions + - [isbool](#isbool) + - [isint](#isint) + - [isnil](#isnil) + - [isnum](#isnum) + - [isobject](#isobject) + - [isrecord](#isrecord) + - [isstr](#isstr) + - [typeof](#typeof) + + +# char functions + +

    chr(val)

    +Convert a numeric value to the character counterpart. +``` +<% chr(10) %> +``` -```<% substr(' hello ', 2, 5) %>``` -## substring(string, start[, end]) -Return a substring starting at 'start'. If 'start' or 'end' is less than 0, it will be the offset from the end of the string. +

    ord(char)

    +Convert a character to the numeric counterpart. +``` +<% ord('a') %> +``` -```<% substring(' hello ', 2, 6) %>``` -## pos(substr, string[, offset]) -Return an offset of substr within string from offset. if offset is less than 0, it will be the offset from the end of the string. +# date functions -``` pos('lo', 'hello') ``` -## dtnow() +

    dtnow()

    Returns the SysUtils now() date time. ``` <% dtnow() %>``` -## fmtdt(format[, dt]) + +

    fmtdt(format[, dt])

    Formats a date time using FormatDateTime ``` <% fmtdt('yyyy-mm-dd', dtnow()) %>``` - + For formatting options, see http://docwiki.embarcadero.com/Libraries/Rio/en/System.SysUtils.FormatDateTime#Description -## fmt(format[, args...]) -Allows a string to be formatted using SysUtils format(). -```<% fmt('%s %s %d', 'hello','world', 123) %> ``` - -For formatting options, see: http://docwiki.embarcadero.com/Libraries/Rio/en/System.SysUtils.Format#Format_Strings -## len(string) -Return the length of a string or an array. -```<% len('hello world') %>``` -## str(any) / isstr(any) -str() casts a variable to a string. isstr() checks if a variable is a string. +# dataset functions + +

    isdataset(object)

    +return true if the object is a TDataSet ``` -<% str(123) + ' = one two three' %> +<% isdataset(ds) %> ``` -## int(any) / isint(any) -int() casts a number to an integer. isint() checks if a variable is an integer. -## num(any) / isnum(any) -num() casts the variable to a number. isnum() checks if a variable is an integer or a float. +

    recordcount(dataset)

    +return the length of a dataset +``` +<% RecordCount(ds) %> +``` -## isnull(any) -isnull() checks if an object is null or not. +# encoding/decoding functions -## split(string, sep) +

    base64decode(string)

    +returns the base64 decoded value +``` +<% base64decode('aGVsbG8gd29ybGQ=') %> +``` -Split the string using a seperator returning an array of string. +

    base64encode(string)

    +returns the base64 encoded value +``` +<% base64encode('hello world') %> +``` +

    htmlescape(string)

    +returns the html escaped value ``` -<% split('hello world', ' ')[0] %> +<% htmlescape('google') %> ``` -## rev(string) +

    htmlunescape(string)

    +returns the html escaped value +``` +<% htmlunescape('&lt;a href=&quot;https://www.google.com&quot;&gt;google&lt;/a&gt;') %> +``` -Reverse a string. +# hashing functions +

    md5(string)

    +returns the md5 hash of the string +``` +<% md5('hello world') %> +``` +

    sha1(string)

    +returns the sha1 hash of the string ``` -<% rev('abc') %> +<% sha1('hello world') %> ``` -## ucfirst(string) +

    sha256(string)

    +returns the sha256 hash of the string +``` +<% sha1('hello world') %> +``` -Uppercase the first letter of a string with the rest being lowercase. +# misc functions +

    isempty(value)

    +Returns true if the collection is an object, it is not nil, and it contains values. If you have custom containers, the rtti unit provides RegisterEmptyObjectCheck() to enhance this method. ``` -<% ucfirst('hello') %> +<% isempty(list) %> +<% isempty(dict) %> +<% isempty(queue) %> +<% isempty(stack) %> +<% isempty(dataset) %> ``` -## uppercase(string) +

    sort(array)

    +Sorts basic arrays of integer, double, extended and string or something enumerable of these types. +``` +<% values := sort(split('g,f,d,s,d,a', ',')) %> +``` -Uppercase a string. +

    templateexists(string)

    +returns true if the template exists +``` +<% templateexists('template') %> +``` + +# numeric functions +

    abs(value)

    +return the absolute value of a value ``` -<% uppercase('heLlo') %> +<% abs(-123.45) %> ``` -## lowercase(string) +

    min(adouble, bdouble)

    +return the minimum of two values +``` +<% min(1,2) %> +``` -Lowercase a string. +

    max(adouble, bdouble)

    +return the maximum of two values ``` -<% lowercase('heLlo') %> +<% max(1,2) %> ``` -## startswith(string, substr[, ignoreCase=true]) -Check if a string starts with another. +# string functions + +

    crnl(len)

    +return a string with len #13#10 ``` -<% startswith('heLlo', 'he') %> +<% crnl(2) %> ``` -## endswith(string, substr[, ignoreCase=true]) + +

    endswith(string, substr[, ignoreCase=true])

    Check if a string ends with another. ``` <% endswith('heLlo', 'lo') %> ``` -## typeof(obj) -Return the class name of an object. +

    fmt(format[, args...])

    +Allows a string to be formatted using SysUtils format(). -``` -<% typeof(myObj) %> -``` +```<% fmt('%s %s %d', 'hello','world', 123) %> ``` -## replace(search, replacement, str) -Replace some text with another. +For formatting options, see: http://docwiki.embarcadero.com/Libraries/Rio/en/System.SysUtils.Format#Format_Strings + +

    len(string)

    +Return the length of a string or an array. + +```<% len('hello world') %>``` + +

    lowercase(string)

    +Lowercase a string. ``` -<% replace('a', 'hello ', 'aaa') %> +<% lowercase('heLlo') %> ``` -## match(text, regex) +

    match(text, regex)

    Matches text in a regular expression. ``` <% match('aaaaaaaaaaaaaa', 'a+') %> ``` For more information, see http://docwiki.embarcadero.com/Libraries/Rio/en/System.RegularExpressions.TRegEx.Matches -## sort(array) -Sorts basic arrays of integer, double, extended and string or something enumerable of these types. +

    nl(len)

    +return a string with len #10 ``` -<% values := sort(split('g,f,d,s,d,a', ',')) %> +<% nl(2) %> ``` -## chr(val) -Convert a numeric value to the character counterpart. + +

    padleft(str, len[, padchar=' '])

    +Pad string with padchar from the left till the string is len long. ``` -<% chr(10) %> +<% padleft('123', 6) %> ``` -## ord(char) -Convert a character to the numeric counterpart. +

    padright(str, len[, padchar=' '])

    +Pad string with padchar from the right till the string is len long. ``` -<% ord('a') %> +<% padright('123', 6) %> ``` -## padleft(str, len[, padchar=' ']) -Pad string with padchar from the left till the string is len long. +

    pos(substr, string[, offset])

    +Return an offset of substr within string from offset. if offset is less than 0, it will be the offset from the end of the string. + +``` pos('lo', 'hello') ``` +

    replace(search, replacement, str)

    +Replace some text with another. ``` -<% padleft('123', 6) %> +<% replace('a', 'hello ', 'aaa') %> ``` -## padright(str, len[, padchar=' ']) -Pad string with padchar from the right till the string is len long. +

    rev(string)

    +Reverse a string. + ``` -<% padright('123', 6) %> +<% rev('abc') %> +``` + +

    spaces(len)

    +return len spaces +``` +<% spaces(2) %> +``` + +

    split(string, sep)

    +Split the string using a seperator returning an array of string. + +``` +<% split('hello world', ' ')[0] %> +``` +

    startswith(string, substr[, ignoreCase=true])

    +Check if a string starts with another. +``` +<% startswith('heLlo', 'he') %> ``` -## tabs(len) +

    substr(string, start[, length])

    +Return a substring starting at 'start'. If 'start' is less than 0, it will be the offset from the end of the string. + +```<% substr(' hello ', 2, 5) %>``` + +

    substring(string, start[, end])

    +Return a substring starting at 'start'. If 'start' or 'end' is less than 0, it will be the offset from the end of the string. + +```<% substring(' hello ', 2, 6) %>``` + + +

    tabs(len)

    Return len tabs ``` <% tabs(2) %> ``` -## spaces(len) -return len spaces +

    trim(string) / trimleft(string) / trimright(string)

    + +Remove whitespace from a string. + +```<% trim(' hello ') %>``` + +

    ucfirst(string)

    +Uppercase the first letter of a string with the rest being lowercase. + ``` -<% spaces(2) %> +<% ucfirst('hello') %> ``` -## crnl(len) -return a string with len #13#10 +

    uppercase(string)

    +Uppercase a string. + ``` -<% crnl(2) %> +<% uppercase('heLlo') %> ``` -## nl(len) -return a string with len #10 +# type conversion functions + +

    bool(any)

    +bool() casts to a boolean. + ``` -<% nl(2) %> +<% bool(true) %> // true +<% bool('true') %> // false +<% bool(false) %> // true +<% bool('false') %> // false +<% bool(1) %> // false +<% bool(0) %> // false +<% bool(10) %> // false +<% bool(-10) %> // false +<% bool(1.23) %> // false ``` -## recordcount(dataset) -return the length of a dataset +

    int(any)

    +int() casts a number to an integer. + ``` -<% RecordCount(ds) %> +<% int(123.45) %> +<% int('123.45') %> ``` -## min(adouble, bdouble) -return the minimum of two values +

    num(any)

    +num() casts the variable to a number. + ``` -<% min(1,2) %> +<% num(true) %> // 1 +<% num(10) %> // 10 +<% num('123') %> // 123
    +<% num('1e6') %> //1000000 ``` -## max(adouble, bdouble) -return the maximum of two values +

    str(any)

    +str() casts a variable to a string. isstr() checks if a variable is a string. ``` -<% max(1,2) %> +<% str(123) + ' = one two three' %> ``` -## abs(value) -return the absolute value of a value + +# type checking functions + +

    isbool(any)

    +isbool() checks if a variable is a boolean. + ``` -<% abs(-123.45) %> +<% isbool(true) %> // true +<% isbool('true') %> // false +<% isbool(false) %> // true +<% isbool('false') %> // false +<% isbool(1) %> // false +<% isbool(0) %> // false +<% isbool(1.23) %> // false +``` + +

    isint(any)

    +isint() checks if a variable is an integer. + +``` +<% isint(123) %> // true +<% isint(123.45) %> // false +<% isint('123.45') %> // false +``` + + +

    isnil(any) / isnull(any)

    +isnull()/isnil() checks if an object is null or not. + +``` +<% isnil(dataset) %> // +``` + +

    isnum(any)

    +isnum() checks if a variable is an number. + +``` +<% isnum(123) %> // true +<% isnum(123.45) %> // true +<% isnum('123.45') %> // false +``` + +

    isobject(any)

    +isobject() checks if a variable is an object. + +``` +<% isobject(obj) %> +``` + +

    isrecord(any)

    +isrecord() checks if a variable is a record. + ``` +<% isrecord(obj) %> +``` + +

    isstr(any)

    +isstr() checks if a variable is an string. + +``` +<% isstr(123) %> // false +<% isstr(bool) %> // false +<% isstr('123.45') %> // true +``` + +

    typeof(obj)

    +Return the class name of an object. + +``` +<% typeof(true) %> // System.Boolean +<% typeof(123) %> // System.Extended +<% typeof(123.45) %> // System.Extended +<% typeof('test') %> // System.string +``` + + From 465e126a63c9b63a3440998c81c5bcfcb2a4de49 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 19:32:40 +0100 Subject: [PATCH 073/138] Fix test --- src/Sempare.Template.Evaluate.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 3d05f62..67b6122 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -874,7 +874,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IIfStmt); exit; LExpr := EvalExpr(AStmt.Condition); LIsObject := LExpr.IsObject; - if LIsObject and not IsEmptyObject(LExpr.AsObject) or not LIsObject and AsBoolean(LExpr) then + if LIsObject and not IsEmptyObject(LExpr.AsObject) or isStrLike(LExpr) and (AsString(LExpr, FContext) <> '') or not LIsObject and AsBoolean(LExpr) then AcceptVisitor(AStmt.TrueContainer, self) else if AStmt.FalseContainer <> nil then AcceptVisitor(AStmt.FalseContainer, self); From 1751ee941b498b314e3a12a8e1b6d9ae86796597 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 21:33:28 +0100 Subject: [PATCH 074/138] Add tests for function: IsObject, IsRecord, IsEmptyObject --- tests/Sempare.Template.TestFunctions.pas | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/Sempare.Template.TestFunctions.pas b/tests/Sempare.Template.TestFunctions.pas index 3c94ee5..4199fbf 100644 --- a/tests/Sempare.Template.TestFunctions.pas +++ b/tests/Sempare.Template.TestFunctions.pas @@ -121,6 +121,12 @@ TFunctionTest = class procedure TestMax; [Test] procedure TestAbs; + [Test] + procedure TestIsObject; + [Test] + procedure TestIsRecord; + [Test] + procedure TestIsEmpty; end; type @@ -137,6 +143,7 @@ implementation {$ENDIF} System.SysUtils, System.Rtti, + System.Generics.Collections, Sempare.Template.Functions, Sempare.Template.Context, Sempare.Template, @@ -536,6 +543,43 @@ procedure TFunctionTest.TestAbs; Assert.AreEqual('123.45', Template.Eval('<% abs(123.45) %>')); end; +procedure TFunctionTest.TestIsRecord; +var + LRecord: record end; + LObject: TObject; +begin + LObject := TObject.Create; + Assert.AreEqual('true', Template.Eval('<% isrecord(_) %>', LRecord)); + Assert.AreEqual('false', Template.Eval('<% isrecord(_) %>', LObject)); + LObject.Free; +end; + +procedure TFunctionTest.TestIsObject; +var + LRecord: record end; + LObject: TObject; +begin + LObject := TObject.Create; + Assert.AreEqual('false', Template.Eval('<% isobject(_) %>', LRecord)); + Assert.AreEqual('true', Template.Eval('<% isobject(_) %>', LObject)); + LObject.Free; +end; + +procedure TFunctionTest.TestIsEmpty; +var + LEmpty: TList; + LNonEmpty: TList; +begin + LEmpty := TList.Create; + LNonEmpty := TList.Create; + LNonEmpty.Add('value'); + + Assert.AreEqual('true', Template.Eval('<% isempty(_) %>', LEmpty)); + Assert.AreEqual('false', Template.Eval('<% isempty(_) %>', LNonEmpty)); + LEmpty.Free; + LNonEmpty.Free; +end; + initialization TDUnitX.RegisterTestFixture(TFunctionTest); From 540d50d870f77fad0672c4369a1145b53276208d Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 22:20:05 +0100 Subject: [PATCH 075/138] Fixed for XE4 --- .../WebBrokerStandalone8080.dpr | 3 +-- demo/WebBrokerStandalone/WebModuleUnit1.pas | 19 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr index 387c774..8b415b5 100644 --- a/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr +++ b/demo/WebBrokerStandalone/WebBrokerStandalone8080.dpr @@ -95,9 +95,8 @@ end; procedure WriteStatus(const AServer: TIdHTTPWebBrokerBridge); begin Writeln(sIndyVersion + AServer.SessionList.Version); - Writeln(sActive + AServer.Active.ToString(TUseBoolStrs.True)); + Writeln(sActive + AServer.Active.ToString()); Writeln(sPort + AServer.DefaultPort.ToString); - Writeln(sSessionID + AServer.SessionIDCookieName); Write(cArrow); end; diff --git a/demo/WebBrokerStandalone/WebModuleUnit1.pas b/demo/WebBrokerStandalone/WebModuleUnit1.pas index dad9126..bc20954 100644 --- a/demo/WebBrokerStandalone/WebModuleUnit1.pas +++ b/demo/WebBrokerStandalone/WebModuleUnit1.pas @@ -42,10 +42,9 @@ procedure TWebModule1.WebModule1IndexHandlerAction(Sender: TObject; Request: TWe var LDemos: TArray; begin - LDemos := [ // - TDemo.Create('Web Broker', 'https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Creating_WebBroker_Applications', 'https://github.com/sempare/sempare-delphi-template-engine/tree/main/demo/WebBrokerStandalone', true), // - TDemo.Create('Horse', 'https://github.com/HashLoad/horse', 'https://github.com/sempare/sempare-delphi-template-engine-horse-demo') // - ]; + setlength(LDemos, 2); + LDemos[0] := TDemo.Create('Web Broker', 'https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Creating_WebBroker_Applications', 'https://github.com/sempare/sempare-delphi-template-engine/tree/main/demo/WebBrokerStandalone', true); + LDemos[1] := TDemo.Create('Horse', 'https://github.com/HashLoad/horse', 'https://github.com/sempare/sempare-delphi-template-engine-horse-demo'); Response.Content := TTemplateRegistry.Instance.Eval('index', LDemos); Handled := true; end; @@ -64,12 +63,12 @@ procedure TWebModule1.WebModule1FormInputAction(Sender: TObject; Request: TWebRe LTemplateData.Title := 'User Details'; LTemplateData.FormName := 'userinfo'; LTemplateData.FormAction := Request.PathInfo; - LTemplateData.Fields := [ // - TField.Create('FirstName', 'firstname'), // - TField.Create('LastName', 'lastname'), // - TField.Create('Email', 'email', 'TEmail') // - ]; - LTemplateData.Buttons := [TButton.Create('Submit', 'submit')]; + setlength(LTemplateData.Fields, 3); + LTemplateData.Fields[0] := TField.Create('FirstName', 'firstname'); + LTemplateData.Fields[1] := TField.Create('LastName', 'lastname'); + LTemplateData.Fields[2] := TField.Create('Email', 'email', 'TEmail'); + setlength(LTemplateData.Buttons, 1); + LTemplateData.Buttons[0] := TButton.Create('Submit', 'submit'); Response.Content := TTemplateRegistry.Instance.Eval('dynform', LTemplateData); Handled := true; end; From 93d435bf6bc877c69bc05a437b56f10f2da6107a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 22:22:46 +0100 Subject: [PATCH 076/138] Fixed for XE4 --- src/Sempare.Template.Functions.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sempare.Template.Functions.pas b/src/Sempare.Template.Functions.pas index 7aa501b..d3e83e8 100644 --- a/src/Sempare.Template.Functions.pas +++ b/src/Sempare.Template.Functions.pas @@ -750,7 +750,7 @@ class function TInternalFuntions.IsObject(const AValue: TValue): boolean; class function TInternalFuntions.IsRecord(const AValue: TValue): boolean; begin - exit(AValue.Kind in [tkRecord, tkMRecord]); + exit(AValue.Kind in [tkRecord{$IFDEF SUPPORT_CUSTOM_MANAGED_RECORDS}, tkMRecord{$ENDIF}]); end; constructor TTemplateFunctions.Create; From 605469cbab9eec6206f21da557103b1b4f30e3cb Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 22:54:52 +0100 Subject: [PATCH 077/138] Fix for XE4 --- src/Sempare.Template.Parser.pas | 62 +++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 88c1258..ca32787 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -558,11 +558,17 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Flatten(const AStmts: TArray): TArray; var LStmt: IStmt; + LFlattenedStmts:TList; begin - result := nil; - for LStmt in AStmts do - begin - result := result + LStmt.Flatten; + LFlattenedStmts:=TList.Create; + try + for LStmt in AStmts do + begin + LFlattenedStmts.AddRange(LStmt.Flatten); + end; + exit(LFlattenedStmts.ToArray); + finally + LFlattenedStmts.Free; end; end; @@ -1424,6 +1430,22 @@ function TTemplateParser.RuleFactor: IExpr; const ONFIRST_ONEND_ONLOOP_ELSE: TTemplateSymbolSet = [vsOnBegin, vsOnEnd, vsOnEmpty, vsBetweenItem, vsEND]; +{$IFDEF SUPPORT_PASS_ARRAY_OF_INTERFACE} +function ArrayOfTemplate(const ATemplates: TArray):TArray; +begin + exit(AStmts); +end; +{$ELSE} +function ArrayOfTemplate(const ATemplates:array of ITemplate):TArray; +var + i : integer; +begin + setlength(result, length(ATemplates)); + for i := Low(atemplates) to High(atemplates) do + result[i] := atemplates[i]; +end; +{$ENDIF} + function TTemplateParser.RuleForStmt: IStmt; var LId: string; @@ -1547,7 +1569,7 @@ function TTemplateParser.RuleForStmt: IStmt; else result := TForRangeStmt.Create(LSymbol.Position, LId, LForOp, LLowValueExpr, LHighValueExpr, LStep, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); result := AddStripStmt(result, LEndStripAction, sdRight); - Optimise([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem]); + Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; function TTemplateParser.RuleFunctionExpr(const ASymbol: string): IExpr; @@ -1679,7 +1701,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; else result := TWhileStmt.Create(LSymbol.Position, LCondition, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); result := AddStripStmt(result, LEndStripAction, sdRight); - Optimise([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem]); + Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; function TTemplateParser.RuleWithStmt: IStmt; @@ -2262,15 +2284,21 @@ procedure TTemplate.Optimise; function Strip(const AArray: TArray): TArray; var LStmt: IStmt; + LStmts:TList; begin - result := nil; - for LStmt in AArray do - begin - if supports(LStmt, IEndStmt) or supports(LStmt, ICommentStmt) or supports(LStmt, IElseStmt) or supports(LStmt, INoopStmt) then + LStmts:=TList.Create; + try + for LStmt in AArray do begin - continue; + if supports(LStmt, IEndStmt) or supports(LStmt, ICommentStmt) or supports(LStmt, IElseStmt) or supports(LStmt, INoopStmt) then + begin + continue; + end; + LStmts.Add(LStmt); end; - result := result + [LStmt]; + exit(LStmts.ToArray); + finally + LStmts.Free; end; end; @@ -2751,8 +2779,13 @@ constructor TCompositeStmt.Create(const AFirstStmt, ASecondStmt: IStmt); end; function TCompositeStmt.Flatten: TArray; +var + LStmts:TArray; begin - result := Sempare.Template.Parser.Flatten([FFirstStmt, FSecondStmt]); + setlength(LStmts, 2); + LStmts[0] := FFirstStmt; + LStmts[1] := FSecondStmt; + exit(Sempare.Template.Parser.Flatten(LStmts)); end; function TCompositeStmt.GetFirstStmt: IStmt; @@ -2855,7 +2888,8 @@ constructor TAbstractStmt.Create(const APosition: IPosition); function TAbstractStmt.Flatten: TArray; begin - result := [self]; + setlength(result, 1); + result[0] := self; end; { TNoopStmt } From a6ad6b6f51bf10a3a07ececbe9c4eacabe374c37 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 22:55:45 +0100 Subject: [PATCH 078/138] Fix for XE4 --- src/Sempare.Template.TemplateRegistry.pas | 54 +++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index 69e4174..82fe9ae 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -103,8 +103,9 @@ TFileTemplate = class(TAbstractProxyTemplate, IRefreshableTemplate) TTemplateRegistry = class strict private class var FTemplateRegistry: TTemplateRegistry; - class constructor Create; - class destructor Destroy; + private + class procedure Initialize; + class procedure Finalize; strict private FLock: TCriticalSection; FTemplates: TDictionary; @@ -165,8 +166,6 @@ TTemplateRegistry = class implementation uses - System.Net.UrlClient, - System.Net.HttpClient, Sempare.Template.ResourceStrings, Sempare.Template, System.IOUtils, @@ -216,17 +215,20 @@ constructor TTemplateRegistry.Create; end; {$IFDEF DEBUG} - FLoadStrategy := [tlsLoadFile, tlsLoadResource]; + setlength(FLoadStrategy, 2); + FLoadStrategy[0] := tlsLoadFile; + FLoadStrategy[1] := tlsLoadResource; FRefreshIntervalS := 5; AutomaticRefresh := true; {$ELSE} FRefreshIntervalS := 10; - FLoadStrategy := [tlsLoadResource]; + setlength(FLoadStrategy, 1); + FLoadStrategy[1] := tlsLoadResource; AutomaticRefresh := false; {$ENDIF} end; -class constructor TTemplateRegistry.Create; +class procedure TTemplateRegistry.Initialize; begin FTemplateRegistry := TTemplateRegistry.Create; end; @@ -242,7 +244,7 @@ destructor TTemplateRegistry.Destroy; inherited; end; -class destructor TTemplateRegistry.Destroy; +class procedure TTemplateRegistry.Finalize; begin FTemplateRegistry.Free; end; @@ -251,16 +253,17 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; var LNameContext: TArray; + LExts: TArray; function LoadFromRegistry(out ATemplate: ITemplate): boolean; var LName: string; - LExt: string; + LExt: integer; begin ATemplate := nil; - for LExt in [FTemplateFileExt, ''] do + for LExt := low(LExts) to high(LExts) do begin - LName := FResourceNameResolver(ATemplateName + LExt, LNameContext); + LName := FResourceNameResolver(ATemplateName + LExts[LExt], LNameContext); try ATemplate := TResourceTemplate.Create(FContext, LName); exit(true); @@ -275,13 +278,13 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; function LoadFromFile(out ATemplate: ITemplate): boolean; var LName: string; - LExt: string; + LExt: integer; begin result := false; ATemplate := nil; - for LExt in [FTemplateFileExt, ''] do + for LExt := low(LExts) to high(LExts) do begin - LName := FFileNameResolver(ATemplateName + FTemplateFileExt, LNameContext); + LName := FFileNameResolver(ATemplateName + LExts[LExt], LNameContext); if TFile.Exists(LName) then begin try @@ -298,18 +301,18 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; function LoadFromCustom(out ATemplate: ITemplate): boolean; var LName: string; - LExt: string; + LExt: integer; begin result := false; ATemplate := nil; if not assigned(FCustomTemplateLoader) then exit; - for LExt in [FTemplateFileExt, ''] do + for LExt := low(LExts) to high(LExts) do begin try - LName := FResourceNameResolver(ATemplateName + LExt, LNameContext); - ATemplate := FCustomTemplateLoader(FContext, LName + FTemplateFileExt); + LName := FResourceNameResolver(ATemplateName + LExts[LExt], LNameContext); + ATemplate := FCustomTemplateLoader(FContext, LName); exit(true); except on e: Exception do @@ -330,6 +333,9 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; FLock.Release; end; result := nil; + setlength(LExts, 2); + LExts[0] := FTemplateFileExt; + LExts[1] := ''; if assigned(FNameContextResolver) then LNameContext := FNameContextResolver else @@ -582,7 +588,11 @@ procedure TFileTemplate.Load(const AFilename: string; const ATime: TDateTime); begin if FModifiedAt = ATime then exit; +{$IFDEF SUPPORT_BUFFERED_STREAM} LStream := TBufferedFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone); +{$ELSE} + LStream := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone); +{$ENDIF} LTemplate := Template.Parse(FContext, LStream); inherited Create(LTemplate); LTemplate.FileName := AFilename; @@ -608,4 +618,12 @@ constructor ETemplateRefreshTooFrequent.Create; inherited CreateRes(@SRefreshTooFrequent); end; +initialization + +TTemplateRegistry.Initialize; + +finalization + +TTemplateRegistry.Finalize; + end. From 46f0ced8bd1274022d527c59140cc851a69fc03c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 22:56:13 +0100 Subject: [PATCH 079/138] Fix for XE4 --- src/Sempare.Template.pas | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index 9ecc38d..b916e34 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -83,7 +83,7 @@ Template = class {$IFDEF MSWINDOWS} private class var FLicenseShown: boolean; - class constructor Create; + class procedure Initialize; {$ENDIF} {$ENDIF} public @@ -375,7 +375,7 @@ class procedure Template.ExtractReferences(const ATemplate: ITemplate; out AVari {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} {$IFDEF MSWINDOWS} -class constructor Template.Create; +class procedure Template.Initialize; begin FLicenseShown := false; end; @@ -403,6 +403,12 @@ initialization GEmpty := TObject.Create; +{$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} +{$IFDEF MSWINDOWS} +Template.Initialize; +{$ENDIF} +{$ENDIF} + finalization GEmpty.Free; From 7c502e971519a6f9903dcacf83910434c7056074 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 23:22:14 +0100 Subject: [PATCH 080/138] Fix includes --- src/Sempare.Template.Compiler.inc | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Sempare.Template.Compiler.inc b/src/Sempare.Template.Compiler.inc index d05cb7c..117a778 100644 --- a/src/Sempare.Template.Compiler.inc +++ b/src/Sempare.Template.Compiler.inc @@ -38,40 +38,44 @@ // ----------------------------------------------- {$IF CompilerVersion >= 25} // from XE4 onwards -{$DEFINE SUPPORT_JSON_DBX true} -{$DEFINE BROKEN_ARRAY_BOUNDS true} +{$DEFINE SUPPORT_JSON_DBX} +{$DEFINE BROKEN_ARRAY_BOUNDS} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 26} // from XE5 onwards {$UNDEF BROKEN_ARRAY_BOUNDS} -{$DEFINE SUPPORT_JSON true} +{$DEFINE SUPPORT_JSON} {$DEFINE SEMPARE_TEMPLATE_FIREDAC} {$ENDIF} // ----------------------------------------------- -{$IF CompilerVersion >= 28} // from XE7 onwards +{$IF CompilerVersion >= 27} // from XE6 onwards {$UNDEF SUPPORT_JSON_DBX} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 28} // from XE7 onwards -{$DEFINE SUPPORT_NET_ENCODING true} +{$DEFINE SUPPORT_NET_ENCODING} +{$DEFINE SUPPORT_CLASS_CONSTRUCTOR} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 29} // from XE8 onwards -{$DEFINE SUPPORT_AS_VARREC true} +{$DEFINE SUPPORT_AS_VARREC} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 30} // from Delphi 10.0 Seatle onwards -{$DEFINE SUPPORT_JSON_BOOL true} -{$DEFINE SUPPORT_ENCODING true} -{$DEFINE SUPPORT_HASH true} +{$DEFINE SUPPORT_JSON_BOOL} +{$DEFINE SUPPORT_ENCODING} +{$DEFINE SUPPORT_HASH} +{$DEFINE SUPPORT_ADD_DYNARRAY} // result := result + [istmt, istmt]; +{$DEFINE SUPPORT_PASS_ARRAY_OF_INTERFACE} // call a method like dosomething([istmt, istmt]) +{$DEFINE SUPPORT_WIN_REGISTRY} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 31} // from Delphi 10.1 Berlin onwards -{$DEFINE SUPPORT_BUFFERED_STREAM true} +{$DEFINE SUPPORT_BUFFERED_STREAM} {$ENDIF} // ----------------------------------------------- {$IF CompilerVersion >= 33} // from Delphi 10.3 Rio onwards -{$DEFINE SUPPORT_CUSTOM_MANAGED_RECORDS true} +{$DEFINE SUPPORT_CUSTOM_MANAGED_RECORDS} {$ENDIF} // ----------------------------------------------- @@ -79,7 +83,7 @@ // {$DEFINE SEMPARE_TEMPLATE_NO_INDY} {$IFNDEF SEMPARE_TEMPLATE_NO_INDY} -{$DEFINE SEMPARE_TEMPLATE_INDY true} +{$DEFINE SEMPARE_TEMPLATE_INDY} {$ENDIF} // ----------------------------------------------- @@ -89,7 +93,7 @@ {$IFDEF SUPPORT_NET_ENCODING} {$UNDEF SEMPARE_TEMPLATE_INDY} {$ENDIF} -{$DEFINE SEMPARE_TEMPLATE_HAS_HTML_ENCODER true} +{$DEFINE SEMPARE_TEMPLATE_HAS_HTML_ENCODER} {$ENDIF} {$IFNDEF SEMPARE_TEMPLATE_HAS_HTML_ENCODER} {$MESSAGE 'No default html encoder present.'} From 764aee9f5a882895a2cf6554e4f248343b51fd03 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 23:22:41 +0100 Subject: [PATCH 081/138] Fix XE6 --- src/Sempare.Template.JSON.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sempare.Template.JSON.pas b/src/Sempare.Template.JSON.pas index e5376eb..928812b 100644 --- a/src/Sempare.Template.JSON.pas +++ b/src/Sempare.Template.JSON.pas @@ -56,7 +56,7 @@ interface TJSONFalse = Data.DBXJSON.TJSONFalse; {$ELSE} TJsonValue = System.JSON.TJsonValue; - TJSONBool = System.JSON.TJSONBool; + //TJSONBool = System.JSON.TJSONBool; // not in XE6 TJSONString = System.JSON.TJSONString; TJSONNumber = System.JSON.TJSONNumber; TJsonObject = System.JSON.TJsonObject; From fde59c7b153df37cc4870f3fab7b4a8e987d8a9c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Mon, 3 Apr 2023 23:23:01 +0100 Subject: [PATCH 082/138] Fix XE4 --- src/Sempare.Template.Util.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sempare.Template.Util.pas b/src/Sempare.Template.Util.pas index 761e94a..62c01cf 100644 --- a/src/Sempare.Template.Util.pas +++ b/src/Sempare.Template.Util.pas @@ -82,7 +82,7 @@ implementation uses SysUtils, WinAPI.Windows, - Win.Registry; + {$IFDEF SUPPORT_WIN_REGISTRY}Win.{$ENDIF}Registry; var GVmwareResolved: boolean; From 93c6551f61498742a7989a258d79ca4dfc63ac76 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:28:35 +0100 Subject: [PATCH 083/138] Parsing must use the context --- demo/VelocityDemo/Sempare.Template.DemoForm.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.pas b/demo/VelocityDemo/Sempare.Template.DemoForm.pas index 27908c9..d2360f0 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.pas +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.pas @@ -164,7 +164,7 @@ procedure TFormRealTime.butEvalClick(Sender: TObject); try if FFilename <> '' then butSave.Enabled := true; - FTemplate := Template.Parse(memoTemplate.Lines.Text); + FTemplate := Template.Parse(FContext, memoTemplate.Lines.Text); // Template.TemplateText := memoTemplate.Lines.Text; Process; // this is a hack so that app does not throw an exception From 5918318ccea9addb5bf6bf4f492bf37bfcfe75a6 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:30:33 +0100 Subject: [PATCH 084/138] Remove experimental tags --- Sempare.Template.Pkg.dpk | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Sempare.Template.Pkg.dpk b/Sempare.Template.Pkg.dpk index 8ac6900..0b0f0ef 100644 --- a/Sempare.Template.Pkg.dpk +++ b/Sempare.Template.Pkg.dpk @@ -1,5 +1,4 @@ -{+ block 'copyright' +} -(*%************************************************************************************************* +(*%************************************************************************************************** * ___ * * / __| ___ _ __ _ __ __ _ _ _ ___ * * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * @@ -7,13 +6,13 @@ * |_| * **************************************************************************************************** * * - * Sempare Templating Engine * + * Sempare Template Engine * * * * * * https://github.com/sempare/sempare-delphi-template-engine * **************************************************************************************************** * * - * Copyright (c) 2019-2021 Sempare Limited * + * Copyright (c) 2019-2023 Sempare Limited * * * * Contact: info@sempare.ltd * * * @@ -31,10 +30,8 @@ * limitations under the License. * * * *************************************************************************************************%*) -{+ end +} -package Sempare.Template.Pkg{+ CompilerVersion +}; +package Sempare.Template.Pkg; -{+ block 'directives' +} {$R *.res} {$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} {$ALIGN 8} @@ -61,17 +58,13 @@ package Sempare.Template.Pkg{+ CompilerVersion +}; {$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} {$IMPLICITBUILD ON} -{+ end +} requires -{+ block 'requires' +} rtl, dbrtl, vcl; -{+ end +} contains -{+ block 'contains' +} Sempare.Template.BlockResolver in 'src\Sempare.Template.BlockResolver.pas', Sempare.Template.Visitor in 'src\Sempare.Template.Visitor.pas', Sempare.Template.Util in 'src\Sempare.Template.Util.pas', @@ -90,6 +83,5 @@ contains Sempare.Template.ResourceStrings in 'src\Sempare.Template.ResourceStrings.pas', Sempare.Template.VariableExtraction in 'src\Sempare.Template.VariableExtraction.pas', Sempare.Template.TemplateRegistry in 'src\Sempare.Template.TemplateRegistry.pas'; -{+ end +} end. From e2caa6e27001c376efa116db9e0e3dfd7eb2d73c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:32:36 +0100 Subject: [PATCH 085/138] Improve logging support --- src/Sempare.Template.TemplateRegistry.pas | 79 ++++++++++++++++++----- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index 82fe9ae..d43b139 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -99,6 +99,7 @@ TFileTemplate = class(TAbstractProxyTemplate, IRefreshableTemplate) TTemplateNameContextResolver = reference to function: TArray; TTemplateNameResolver = reference to function(const AName: string; const AContext: TArray): string; TTempateLogException = reference to procedure(const AException: Exception); + TTempateLogMessage = reference to procedure(const AMessage: string; const args: array of const); TTemplateRegistry = class strict private @@ -122,7 +123,8 @@ TTemplateRegistry = class FAutomaticRefresh: boolean; FCustomTemplateLoader: TTemplateResolver; FNameContextResolver: TTemplateNameContextResolver; - FLogException: TTempateLogException; + FExceptionLogger: TTempateLogException; + FLogger: TTempateLogMessage; procedure Refresh; @@ -131,11 +133,13 @@ TTemplateRegistry = class procedure SetLoadStrategy(const Value: TArray); procedure SetTemplateFileExt(const Value: string); procedure LogException(const AException: Exception); + procedure Log(const AMsg: string; const AArgs: array of const); public constructor Create(); destructor Destroy; override; function GetTemplate(const ATemplateName: string): ITemplate; overload; + procedure RemoveTemplate(const ATemplateName: string); procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate); overload; procedure Eval(const AOutputStream: TStream; const ATemplate: ITemplate; const AData: T); overload; @@ -159,8 +163,8 @@ TTemplateRegistry = class property AutomaticRefresh: boolean read FAutomaticRefresh write SetAutomaticRefresh; property CustomTemplateLoader: TTemplateResolver read FCustomTemplateLoader write FCustomTemplateLoader; property NameContextResolver: TTemplateNameContextResolver read FNameContextResolver write FNameContextResolver; - property ExceptionLogger: TTempateLogException read FLogException write FLogException; - + property ExceptionLogger: TTempateLogException read FExceptionLogger write FExceptionLogger; + property Logger: TTempateLogMessage read FLogger write FLogger; end; implementation @@ -168,6 +172,9 @@ implementation uses Sempare.Template.ResourceStrings, Sempare.Template, +{$IFDEF DEBUG} + Sempare.Template.PrettyPrint, +{$ENDIF} System.IOUtils, System.DateUtils; @@ -255,7 +262,7 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; LNameContext: TArray; LExts: TArray; - function LoadFromRegistry(out ATemplate: ITemplate): boolean; + function LoadFromResource(out ATemplate: ITemplate): boolean; var LName: string; LExt: integer; @@ -266,10 +273,14 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; LName := FResourceNameResolver(ATemplateName + LExts[LExt], LNameContext); try ATemplate := TResourceTemplate.Create(FContext, LName); + Log('Loaded template from resource: %s', [LName]); exit(true); except on e: Exception do - LogException(e); + if LExt = high(LExts) then + begin + LogException(e); + end; end; end; exit(false); @@ -289,10 +300,14 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; begin try ATemplate := TFileTemplate.Create(FContext, LName); + Log('Loaded template from file: %s', [LName]); exit(true); except on e: Exception do - LogException(e); + if LExt = high(LExts) then + begin + LogException(e); + end; end; end; end; @@ -313,10 +328,14 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; try LName := FResourceNameResolver(ATemplateName + LExts[LExt], LNameContext); ATemplate := FCustomTemplateLoader(FContext, LName); + Log('Loaded template from custom loader: %s', [LName]); exit(true); except on e: Exception do - LogException(e); + if LExt = high(LExts) then + begin + LogException(e); + end; end; end; end; @@ -344,7 +363,7 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; begin case LLoadStrategy of tlsLoadResource: - if LoadFromRegistry(result) then + if LoadFromResource(result) then break; tlsLoadFile: if LoadFromFile(result) then @@ -364,10 +383,16 @@ function TTemplateRegistry.GetTemplate(const ATemplateName: string): ITemplate; end; end; +procedure TTemplateRegistry.Log(const AMsg: string; const AArgs: array of const); +begin + if assigned(FLogger) then + FLogger(AMsg, AArgs); +end; + procedure TTemplateRegistry.LogException(const AException: Exception); begin - if assigned(FLogException) then - FLogException(AException); + if assigned(FExceptionLogger) then + FExceptionLogger(AException); end; function TTemplateRegistry.Eval(const ATemplateName: string): string; @@ -380,12 +405,12 @@ function TTemplateRegistry.Eval(const ATemplateName: string): string; function TTemplateRegistry.Eval(const ATemplate: ITemplate; const AData: T): string; begin - + exit(Template.Eval(FContext, ATemplate, AData)); end; procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplate: ITemplate; const AData: T); begin - + Template.Eval(FContext, ATemplate, AData, AOutputStream); end; procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplateName: string); @@ -406,12 +431,12 @@ procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplat procedure TTemplateRegistry.Eval(const AOutputStream: TStream; const ATemplate: ITemplate); begin - + Template.Eval(FContext, ATemplate, AOutputStream); end; function TTemplateRegistry.Eval(const ATemplate: ITemplate): string; begin - + exit(Template.Eval(FContext, ATemplate)); end; function TTemplateRegistry.Eval(const ATemplateName: string; const AData: T): string; @@ -441,6 +466,17 @@ procedure TTemplateRegistry.Refresh; end; end; +procedure TTemplateRegistry.RemoveTemplate(const ATemplateName: string); +begin + FLock.Acquire; + try + FContext.RemoveTemplate(ATemplateName); + FTemplates.Remove(ATemplateName); + finally + FLock.Release; + end; +end; + procedure TTemplateRegistry.SetAutomaticRefresh(const Value: boolean); begin if FAutomaticRefresh = Value then @@ -585,6 +621,8 @@ procedure TFileTemplate.Load(const AFilename: string; const ATime: TDateTime); var LStream: TStream; LTemplate: ITemplate; +{$IFDEF DEBUG} + LStr: string; {$ENDIF} begin if FModifiedAt = ATime then exit; @@ -593,7 +631,18 @@ procedure TFileTemplate.Load(const AFilename: string; const ATime: TDateTime); {$ELSE} LStream := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone); {$ENDIF} - LTemplate := Template.Parse(FContext, LStream); + try + LTemplate := Template.Parse(FContext, LStream); + except + on e: Exception do + begin + writeln(e.message); + raise; + end; + end; +{$IFDEF DEBUG} + LStr := Template.PrettyPrint(LTemplate); +{$ENDIF} inherited Create(LTemplate); LTemplate.FileName := AFilename; FModifiedAt := ATime; From b99bfe7d2e597282364ac1d8dcc10b7a46af5606 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:33:31 +0100 Subject: [PATCH 086/138] Expose ETemplateNotResolved --- src/Sempare.Template.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index b916e34..03ca936 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -77,6 +77,8 @@ interface TTemplateRegistry = Sempare.Template.TemplateRegistry.TTemplateRegistry; TTemplateLoadStrategy = Sempare.Template.TemplateRegistry.TTemplateLoadStrategy; TSempareServerPages = TTemplateRegistry; + ETemplateNotResolved = Sempare.Template.TemplateRegistry.ETemplateNotResolved; + TTempateLogMessage = Sempare.Template.TemplateRegistry.TTempateLogMessage; Template = class {$IFNDEF SEMPARE_TEMPLATE_CONFIRM_LICENSE} From 01e566b386917e289782e0d5aa536c18cbf1cea2 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:34:13 +0100 Subject: [PATCH 087/138] Formatting --- src/Sempare.Template.Parser.pas | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index ca32787..1ea3f05 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -34,6 +34,8 @@ interface +{$I 'Sempare.Template.Compiler.inc'} + uses System.Classes, Sempare.Template.AST, @@ -558,9 +560,9 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Flatten(const AStmts: TArray): TArray; var LStmt: IStmt; - LFlattenedStmts:TList; + LFlattenedStmts: TList; begin - LFlattenedStmts:=TList.Create; + LFlattenedStmts := TList.Create; try for LStmt in AStmts do begin @@ -568,7 +570,7 @@ function Flatten(const AStmts: TArray): TArray; end; exit(LFlattenedStmts.ToArray); finally - LFlattenedStmts.Free; + LFlattenedStmts.Free; end; end; @@ -1431,18 +1433,20 @@ function TTemplateParser.RuleFactor: IExpr; ONFIRST_ONEND_ONLOOP_ELSE: TTemplateSymbolSet = [vsOnBegin, vsOnEnd, vsOnEmpty, vsBetweenItem, vsEND]; {$IFDEF SUPPORT_PASS_ARRAY_OF_INTERFACE} -function ArrayOfTemplate(const ATemplates: TArray):TArray; + +function ArrayOfTemplate(const ATemplates: TArray): TArray; begin - exit(AStmts); + exit(ATemplates); end; {$ELSE} -function ArrayOfTemplate(const ATemplates:array of ITemplate):TArray; + +function ArrayOfTemplate(const ATemplates: array of ITemplate): TArray; var - i : integer; + i: integer; begin setlength(result, length(ATemplates)); - for i := Low(atemplates) to High(atemplates) do - result[i] := atemplates[i]; + for i := Low(ATemplates) to High(ATemplates) do + result[i] := ATemplates[i]; end; {$ENDIF} @@ -2284,9 +2288,9 @@ procedure TTemplate.Optimise; function Strip(const AArray: TArray): TArray; var LStmt: IStmt; - LStmts:TList; + LStmts: TList; begin - LStmts:=TList.Create; + LStmts := TList.Create; try for LStmt in AArray do begin @@ -2780,7 +2784,7 @@ constructor TCompositeStmt.Create(const AFirstStmt, ASecondStmt: IStmt); function TCompositeStmt.Flatten: TArray; var - LStmts:TArray; + LStmts: TArray; begin setlength(LStmts, 2); LStmts[0] := FFirstStmt; From 8bf9b2e9fd398a1b83235f3396c77ed6a5278ad1 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:34:40 +0100 Subject: [PATCH 088/138] Add Context.RemoveTemplate --- src/Sempare.Template.Context.pas | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index 9923586..9249b43 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -101,6 +101,7 @@ interface function TryGetTemplate(const AName: string; out ATemplate: ITemplate): boolean; function GetTemplate(const AName: string): ITemplate; procedure SetTemplate(const AName: string; const ATemplate: ITemplate); + procedure RemoveTemplate(const AName: string); function GetTemplateResolver: TTemplateResolver; procedure SetTemplateResolver(const AResolver: TTemplateResolver); @@ -277,6 +278,7 @@ TTemplateContext = class(TInterfacedObject, ITemplateContext, ITemplateContext function TryGetTemplate(const AName: string; out ATemplate: ITemplate): boolean; function GetTemplate(const AName: string): ITemplate; procedure SetTemplate(const AName: string; const ATemplate: ITemplate); + procedure RemoveTemplate(const AName: string); function GetTemplateResolver: TTemplateResolver; procedure SetTemplateResolver(const AResolver: TTemplateResolver); @@ -498,6 +500,16 @@ procedure TTemplateContext.RemoveBlock(const AName: string); FEvaluationContext.RemoveBlock(AName); end; +procedure TTemplateContext.RemoveTemplate(const AName: string); +begin + FLock.Enter; + try + FTemplates.Remove(AName); + finally + FLock.Leave; + end; +end; + function TTemplateContext.GetScriptEndStripToken: string; begin exit(FEndStripToken); From a35141b6f2042ddd30bb68839ecd4f733af8010f Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 4 Apr 2023 22:35:09 +0100 Subject: [PATCH 089/138] Fixed lexing bug leaking on end of script --- src/Sempare.Template.Lexer.pas | 65 +++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index b305fc5..6779551 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -308,34 +308,49 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(ValueToken(vsString)); end; - function isEndOfScript(out aResult: ITemplateSymbol; const AStripAction: TStripAction; const AGetInput: Boolean = false): Boolean; - begin - if AGetInput then - GetInput; - if CharInSet(FCurrent.Input, [FEndScript[1], FEndStripScript[1]]) then + function isEndOfScript(const ALastChar: char; out aResult: ITemplateSymbol; const AStripAction: TStripAction): Boolean; + + function CheckEnd(const ACurrent, ANext: char; const AStripAction: TStripAction): Boolean; begin - if FCurrent.Input = FEndScript[1] then - LEndExpect := FEndScript[2] - else - LEndExpect := FEndStripScript[2]; - if Expecting(LEndExpect) then + if CharInSet(ACurrent, [FEndScript[1], FEndStripScript[1]]) then begin - LEndStripWS := FCurrent.Input = FEndStripScript[1]; - GetInput; - if FAccumulator.length > 0 then - begin - aResult := ValueToken(vsText); - FNextToken := SimpleToken(VsEndScript, LEndStripWS, AStripAction); - end + if ACurrent = FEndScript[1] then + LEndExpect := FEndScript[2] else + LEndExpect := FEndStripScript[2]; + if ANext = LEndExpect then begin - aResult := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + LEndStripWS := ACurrent = FEndStripScript[1]; + if AStripAction = saNone then + GetInput; + if FAccumulator.length > 0 then + begin + aResult := ValueToken(vsText); + FNextToken := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + end + else + begin + aResult := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + end; + FState := SText; + exit(True); end; - FState := SText; - exit(True); end; + exit(false); + end; + + var + LGetInput: Boolean; + begin + LGetInput := AStripAction <> saNone; + if LGetInput then + begin + GetInput; + Result := CheckEnd(ALastChar, FCurrent.Input, AStripAction); + if Result then + exit; end; - exit(false); + exit(CheckEnd(FCurrent.Input, FLookahead.Input, saNone)); end; begin @@ -418,21 +433,21 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(SimpleToken(vsQUESTION)); '+': begin - if isEndOfScript(Result, TStripAction.saWhitespaceAndNLButOne, True) then + if isEndOfScript('+', Result, TStripAction.saWhitespaceAndNLButOne) then exit else exit(SimpleToken(vsPLUS, false, saNone, false)); end; '-': begin - if isEndOfScript(Result, TStripAction.saWhitespace, True) then + if isEndOfScript('-', Result, TStripAction.saWhitespace) then exit else exit(SimpleToken(vsMinus, false, saNone, false)); end; '*': begin - if isEndOfScript(Result, TStripAction.saWhitespaceAndNL, True) then + if isEndOfScript('*', Result, TStripAction.saWhitespaceAndNL) then exit else exit(SimpleToken(vsMULT, false, saNone, false)); @@ -486,7 +501,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(SimpleToken(vsCOLON)); else begin - if isEndOfScript(Result, TStripAction.saNone) then + if isEndOfScript(#0, Result, TStripAction.saNone) then exit; end; end; From 309e61b658d0868d6d5282e4669dba54fe551e5a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Thu, 6 Apr 2023 20:33:53 +0100 Subject: [PATCH 090/138] initial remix work in progress --- docs/whitespace-removal.md | 17 ++ src/Sempare.Template.AST.pas | 30 ++- src/Sempare.Template.Compiler.inc | 2 +- src/Sempare.Template.Evaluate.pas | 20 +- src/Sempare.Template.Lexer.pas | 113 +++++++-- src/Sempare.Template.Parser.pas | 304 ++++++++++++++++--------- src/Sempare.Template.Visitor.pas | 12 + tests/Sempare.Template.TestInclude.pas | 2 +- tests/Sempare.Template.TestJson.pas | 2 +- 9 files changed, 363 insertions(+), 139 deletions(-) diff --git a/docs/whitespace-removal.md b/docs/whitespace-removal.md index 8c3f8bb..3787e7c 100644 --- a/docs/whitespace-removal.md +++ b/docs/whitespace-removal.md @@ -8,6 +8,22 @@ v1.7.0 introduces some new concepts is still experimental and will change slight Removing whitespace is a tricky problem in templates. In the template engine, attempts have been made to address this in a number of ways. + + + + 1 2 + <% if a = 1 -%> + a + b + <% end %> + + +2... when a newline is reached, action is applied to everything to the left +1... from the newline, the action is applied + + + + ### Using script tag hints Scripts start with <% and close with %>. These tags may have additional hints to assist with the removal of whitespace. @@ -17,6 +33,7 @@ Scripts start with <% and close with %>. These tags may have additional hints to | - | Removes whitespace only. | | + | Removes whitespace as well as a newline, but leaves one space. | | * | Removes whitespace as well as a newline. | +| _ | Remove only indent level | #### Using - diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index e9572b9..e236a86 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -55,6 +55,7 @@ ETemplate = class(Exception); TStripAction = ( // saWhitespace, // + // saUnindent, // saWhitespaceAndNL, // saWhitespaceAndNLButOne, // saNone // @@ -145,9 +146,14 @@ ETemplate = class(Exception); vsSemiColon, // vsExtends, // - vsBlock // + vsBlock, // + + vsNewLine, // + vsWhiteSpace // ); + TTemplateSymbolSet = set of TTemplateSymbol; + IPosition = interface ['{2F087E1F-42EE-44D4-B928-047AA1788F50}'] function GetFilename: string; @@ -195,6 +201,8 @@ ETemplate = class(Exception); IStmt = interface(ITemplateVisitorHost) ['{6D37028E-A0C0-41F1-8A59-EDC0C9ADD9C7}'] function Flatten: TArray; + function GetHasEnd: boolean; + property HasEnd: boolean read GetHasEnd; end; IDebugStmt = interface(IStmt) @@ -243,14 +251,20 @@ ETemplate = class(Exception); ['{FB4CC3AB-BFEC-4189-B555-153DDA490D15}'] end; - TStripDirection = (sdLeft, sdRight); + TStripDirection = (sdLeft, sdRight, sdEnd, sdBeforeNewLine = sdRight, sdAfterNewLine = sdLeft); IStripStmt = interface(IStmt) ['{3313745B-D635-4453-9808-660DC462E15C}'] function GetDirection: TStripDirection; function GetAction: TStripAction; + function GetHasEnd: boolean; + procedure SetHasEnd(const AHasEnd: boolean); + function GetIndent: string; + procedure SetIndent(const AIndent: string); property Direction: TStripDirection read GetDirection; property Action: TStripAction read GetAction; + property HasEnd: boolean read GetHasEnd write SetHasEnd; + property Indent: string read GetIndent write SetIndent; end; ICompositeStmt = interface(IStmt) @@ -423,6 +437,14 @@ ETemplate = class(Exception); property Value: TValue read GetValue; end; + INewLineExpr = interface(IValueExpr) + ['{A140C174-1C92-4C4C-AD8C-5A46066B2338}'] + end; + + IWhitespaceExpr = interface(IValueExpr) + ['{65ACCA2B-B671-42E9-8ED4-179B11C36AA0}'] + end; + IVariableExpr = interface(IExpr) ['{AE35E829-A756-4A1C-9F41-01CF3DD34096}'] function GetVariable: string; @@ -515,6 +537,7 @@ ETemplate = class(Exception); procedure Visit(const AExpr: IBinopExpr); overload; procedure Visit(const AExpr: IUnaryExpr); overload; procedure Visit(const AExpr: IVariableExpr); overload; + procedure Visit(const AExpr: INewLineExpr); overload; procedure Visit(const AExpr: IValueExpr); overload; procedure Visit(const AExpr: ITernaryExpr); overload; procedure Visit(const AExpr: IArrayExpr); overload; @@ -559,10 +582,11 @@ ETemplate = class(Exception); end; const - StripDirectionStr: array [TStripDirection] of string = ('sdLeft', 'sdRight'); + StripDirectionStr: array [TStripDirection] of string = ('sdAfterNewLine', 'sdBeforeNewLine', 'sdEnd'); StripActionStr: array [TStripAction] of string = ( // 'saWhitespace', // + //'saUnindent', // 'saWhitespaceAndNL', // 'saWhitespaceAndNLButOne', // 'saNone' // diff --git a/src/Sempare.Template.Compiler.inc b/src/Sempare.Template.Compiler.inc index 117a778..983e759 100644 --- a/src/Sempare.Template.Compiler.inc +++ b/src/Sempare.Template.Compiler.inc @@ -34,7 +34,7 @@ // ----------------------------------------------- // Uncomment the following to confirm the license conditions -// {$DEFINE SEMPARE_TEMPLATE_CONFIRM_LICENSE} +{$DEFINE SEMPARE_TEMPLATE_CONFIRM_LICENSE} // ----------------------------------------------- {$IF CompilerVersion >= 25} // from XE4 onwards diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 67b6122..075517d 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -115,6 +115,7 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor, IEvaluationTemplateVi procedure Visit(const AExpr: IVariableExpr); overload; override; procedure Visit(const AExpr: IVariableDerefExpr); overload; override; procedure Visit(const AExpr: IValueExpr); overload; override; + procedure Visit(const AExprList: IExprList); overload; override; procedure Visit(const AExpr: ITernaryExpr); overload; override; procedure Visit(const AExpr: IEncodeExpr); overload; override; @@ -910,7 +911,17 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IPrintStmt); begin if HasBreakOrContinue then exit; + if supports(AStmt.Expr, INewlineExpr) then + begin + // FStreamWriter.Write('[beforenl]'); + end; FStreamWriter.Write(EvalExprAsString(AStmt.Expr)); + if supports(AStmt.Expr, INewlineExpr) then + begin + // FStreamWriter.Write('[afternl]'); + + end; + end; function CastArg(const AValue: TValue; const AType: TRttiType; const AContext: ITemplateContext): TValue; @@ -1288,10 +1299,11 @@ procedure TNewLineStreamWriter.DoWrite(); const STRIP_CHARS: array [TStripAction] of set of char = ( // - [' ', #9], // - [' ', #9, #13, #10], // - [' ', #9, #13, #10], // - [] // + [' ', #9], // saWhitespace + // [' ', #9], // saUnindent + [' ', #9, #13, #10], // saWhitespaceAndNL + [' ', #9, #13, #10], // saWhitespaceAndNLButOne + [] // saNone ); {$WARN WIDECHAR_REDUCED ON} diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index 6779551..cef14e0 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -310,7 +310,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; function isEndOfScript(const ALastChar: char; out aResult: ITemplateSymbol; const AStripAction: TStripAction): Boolean; - function CheckEnd(const ACurrent, ANext: char; const AStripAction: TStripAction): Boolean; + function CheckEnd(const ACurrent, ANext: char; const AStripAction: TStripAction; const AGetInput: Boolean = false): Boolean; begin if CharInSet(ACurrent, [FEndScript[1], FEndStripScript[1]]) then begin @@ -321,7 +321,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; if ANext = LEndExpect then begin LEndStripWS := ACurrent = FEndStripScript[1]; - if AStripAction = saNone then + if (AStripAction = saNone) or AGetInput then GetInput; if FAccumulator.length > 0 then begin @@ -349,6 +349,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; Result := CheckEnd(ALastChar, FCurrent.Input, AStripAction); if Result then exit; + exit(CheckEnd(FCurrent.Input, FLookahead.Input, AStripAction, True)); end; exit(CheckEnd(FCurrent.Input, FLookahead.Input, saNone)); end; @@ -522,7 +523,6 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; var LLine: integer; LPosition: integer; - LLastChar, LCurChar: char; LIsStartStripWSToken: Boolean; LState: TStripAction; @@ -543,21 +543,57 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; GetInput; end; - function ValueToken(const ASymbol: TTemplateSymbol): ITemplateSymbol; + function ValueToken(const ASymbol: TTemplateSymbol; const AGetNext: Boolean = True): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; Result := TTemplateValueSymbol.Create(LPosition, ASymbol, FAccumulator.ToString); FAccumulator.Clear; - GetInput; + if AGetNext then + GetInput; + end; + + procedure AccumulateChars(const Achars: TCharSet; const ATransform: TFunc); + begin + while not FCurrent.Eof and (CharInSet(FCurrent.Input, Achars)) do + begin + FAccumulator.Append(FCurrent.Input); + GetInput; + end; + end; + + function CanProduceToken(const AToken: TTemplateSymbol; const Achars: TCharSet; out ASymbol: ITemplateSymbol; const ATransform: TFunc; const AFinal: TFunc): Boolean; + var + lstr: string; + begin + if not CharInSet(FCurrent.Input, Achars) then + exit(false); + + if FAccumulator.length > 0 then + begin + ASymbol := ValueToken(vsText, false); + AccumulateChars(Achars, ATransform); + lstr := AFinal(FAccumulator.ToString); + FAccumulator.Clear; + FAccumulator.Append(lstr); + FNextToken := ValueToken(vsNewLine, false); + end + else + begin + AccumulateChars(Achars, ATransform); + lstr := AFinal(FAccumulator.ToString); + FAccumulator.Clear; + FAccumulator.Append(lstr); + ASymbol := ValueToken(vsNewLine, false); + end; + exit(True); end; begin FAccumulator.Clear; LLine := FLine; LPosition := FPos; - LLastChar := #0; if FCurrent.Input = #0 then GetInput; while not FCurrent.Eof do @@ -567,6 +603,8 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; begin Result := ValueToken(vsText); case FLookahead.Input of + // '_': + // LState := TStripAction.saUnindent; '-': LState := TStripAction.saWhitespace; '+': @@ -581,21 +619,57 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; FState := SScript; FNextToken := SimpleToken(VsStartScript, LIsStartStripWSToken, LState); exit(); - end - else + end; + + if CanProduceToken(vsNewLine, [#13, #10], Result, + function(c: char): char + begin + exit(c); + end, + function(s: string): string + begin + if eoStripRecurringNewlines in FOptions then + exit(FContext.NewLine) + else + exit(s); + end) then begin - LCurChar := FCurrent.Input; - if (eoConvertTabsToSpaces in FOptions) and (LCurChar = #9) then - LCurChar := ' '; - if (eoStripRecurringSpaces in FOptions) and (LLastChar = ' ') and (LCurChar = ' ') then - GetInput - else + FCurrent.Input := FCurrent.Input; + exit; + end; + + if CanProduceToken(vsWhiteSpace, [' ', #9], Result, + function(c: char): char begin - FAccumulator.Append(LCurChar); - LLastChar := LCurChar; - GetInput; - end; + if (eoConvertTabsToSpaces in FOptions) and (c = #9) then + exit(' ') + else + exit(c); + end, + function(s: string): string + var + i: integer; + l: char; + begin + if not(eoStripRecurringSpaces in FOptions) then + exit(s); + Result := ''; + l := #0; + for i := Low(s) to High(s) do + begin + if l = s[i] then + continue; + l := s[i]; + Result := Result + l; + end; + end) then + begin + FCurrent.Input := FCurrent.Input; + exit; end; + + FAccumulator.Append(FCurrent.Input); + GetInput; end; if FAccumulator.length > 0 then @@ -789,6 +863,9 @@ initialization AddSymKeyword(',', vsComma); AddSymKeyword(';', vsSemiColon); +AddSymKeyword({$IFDEF MSWINDOWS}#13#10{$ELSE}#10{$ENDIF}, vsNewLine); +AddSymKeyword(' ', vsWhiteSpace); + finalization GKeywords.Free; diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 1ea3f05..81170e5 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -109,6 +109,7 @@ TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisito TAbstractStmt = class abstract(TAbstractBase, IStmt) private function Flatten: TArray; virtual; + function GetHasEnd: boolean; virtual; public constructor Create(const APosition: IPosition); end; @@ -134,6 +135,7 @@ TExtendsStmt = class(TAbstractStmt, IExtendsStmt) function GetName: IExpr; function GetBlockContainer: ITemplate; function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; + function GetHasEnd: boolean; override; public constructor Create(const APosition: IPosition; const AName: IExpr; const ABlockContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; @@ -146,6 +148,7 @@ TBlockStmt = class(TAbstractStmt, IBlockStmt) function GetName: IExpr; function GetContainer: ITemplate; function NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; + function GetHasEnd: boolean; override; public constructor Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); procedure Accept(const AVisitor: ITemplateVisitor); override; @@ -198,8 +201,14 @@ TStripStmt = class(TAbstractStmt, IStripStmt) private FDirection: TStripDirection; FAction: TStripAction; + FHasEnd: boolean; + FIndent: string; + function GetIndent: string; + procedure SetIndent(const AIndent: string); function GetDirection: TStripDirection; function GetAction: TStripAction; + function GetHasEnd: boolean; override; + procedure SetHasEnd(const AHasEnd: boolean); procedure Accept(const AVisitor: ITemplateVisitor); override; public constructor Create(const ADirection: TStripDirection; const AAction: TStripAction); @@ -240,6 +249,7 @@ TIfStmt = class(TAbstractStmt, IIfStmt) function GetTrueContainer: ITemplate; function GetFalseContainer: ITemplate; procedure Accept(const AVisitor: ITemplateVisitor); override; + function GetHasEnd: boolean; override; public constructor Create(const APosition: IPosition; const ACondition: IExpr; const ATrueContainer: ITemplate; const AFalseContainer: ITemplate); end; @@ -267,6 +277,7 @@ TDefineTemplateStmt = class(TAbstractStmtWithContainer, IDefineTemplateStmt) FName: IExpr; function GetName: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function GetHasEnd: boolean; override; public constructor Create(const APosition: IPosition; const AName: IExpr; const AContainer: ITemplate); end; @@ -276,6 +287,7 @@ TWithStmt = class(TAbstractStmtWithContainer, IWithStmt) FExpr: IExpr; function GetExpr: IExpr; procedure Accept(const AVisitor: ITemplateVisitor); override; + function GetHasEnd: boolean; override; public constructor Create(const APosition: IPosition; const AExpr: IExpr; const AContainer: ITemplate); end; @@ -291,6 +303,7 @@ TLoopStmt = class(TAbstractStmtWithContainer, ILoopStmt) function GetOnEndContainer: ITemplate; function GetOnEmptyContainer: ITemplate; function GetBetweenItemContainer: ITemplate; + function GetHasEnd: boolean; override; public constructor Create(APosition: IPosition; AContainer: ITemplate; AOnBegin, AOnEnd, AOnEmpty, ABetweenItem: ITemplate); end; @@ -382,6 +395,20 @@ TValueExpr = class(TAbstractExpr, IValueExpr) constructor Create(const APosition: IPosition; const AValue: TValue); end; + TNewLineExpr = class(TValueExpr, INewLineExpr) + private + procedure Accept(const AVisitor: ITemplateVisitor); override; + public + constructor Create(const APosition: IPosition; const AValue: TValue); + end; + + TWhitespaceExpr = class(TValueExpr, IWhitespaceExpr) + private + procedure Accept(const AVisitor: ITemplateVisitor); override; + public + constructor Create(const APosition: IPosition; const AValue: TValue); + end; + TAbstractExprWithExprList = class abstract(TAbstractExpr) private FExprList: IExprList; @@ -499,12 +526,13 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) FLexer: ITemplateLexer; FContainerStack: TStack; FOptions: TParserOptions; - + FStripAction: TStripAction; function PushContainer: ITemplate; function PopContainer: ITemplate; function CurrentContainer: ITemplate; function lookaheadValue: string; + function MatchValues(const ASymbols: TTemplateSymbolSet; out ASymbol: TTemplateSymbol): string; function MatchValue(const ASymbol: TTemplateSymbol): string; procedure Match(ASymbol: ITemplateSymbol); overload; inline; function Match(const ASymbol: TTemplateSymbol): TStripAction; overload; @@ -695,15 +723,18 @@ function TTemplateParser.AddEndStripStmt(const ATemplate: ITemplate; const AStri function TTemplateParser.AddStripStmt(const AStmt: IStmt; const AStripAction: TStripAction; const ADirection: TStripDirection): IStmt; var - LStripStmt: IStmt; + LStripStmt: IStripStmt; begin if AStripAction = saNone then exit(AStmt) else begin LStripStmt := TStripStmt.Create(ADirection, AStripAction); + LStripStmt.HasEnd := AStmt.HasEnd; if ADirection = sdLeft then - exit(TCompositeStmt.Create(LStripStmt, AStmt)) + begin + exit(TCompositeStmt.Create(LStripStmt, AStmt)); + end else exit(TCompositeStmt.Create(AStmt, LStripStmt)); end; @@ -728,27 +759,22 @@ function TTemplateParser.RuleIfStmt: IStmt; LOptions: IPreserveValue; LSymbol: ITemplateSymbol; LEndStripAction: TStripAction; - LTrueStripAction: TStripAction; - LFalseStripAction: TStripAction; + LStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poAllowEnd, poAllowElIf]); LSymbol := FLookahead; + LStripAction := FStripAction; Match(vsIF); LConditionalExpr := RuleExpression; - - LTrueStripAction := Match(vsEndScript); + LEndStripAction := Match(vsEndScript); // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; - AddEndStripStmt(LTrueContainer, LTrueStripAction); - RuleStmts(LTrueContainer, IF_ELIF_END); PopContainer; - PushContainer; LFalseContainer := self.CurrentContainer; LContainerAdd := LFalseContainer as ITemplateAdd; - if FLookahead.Token = vsELIF then begin while (FLookahead.Token = vsELIF) do @@ -759,14 +785,12 @@ function TTemplateParser.RuleIfStmt: IStmt; else if FLookahead.Token = vsElse then begin Match(vsElse); - LFalseStripAction := Match(vsEndScript); - AddEndStripStmt(LFalseContainer, LFalseStripAction); + Match(vsEndScript); RuleStmts(LFalseContainer, [vsEND]); end; PopContainer; Match(vsEND); - LEndStripAction := Match(vsEndScript); - + Match(vsEndScript); if (eoEvalEarly in FContext.Options) and IsValue(LConditionalExpr) then begin if AsBoolean(AsValue(LConditionalExpr)) then @@ -775,7 +799,8 @@ function TTemplateParser.RuleIfStmt: IStmt; exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) end; result := TIfStmt.Create(LSymbol.Position, LConditionalExpr, LTrueContainer, LFalseContainer); - result := AddStripStmt(result, LEndStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); end; function TTemplateParser.RuleIgnoreNewline: IStmt; @@ -787,20 +812,16 @@ function TTemplateParser.RuleIgnoreNewline: IStmt; LEndStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); - LSymbol := FLookahead; Match(vsIgnoreNL); LStripAction := Match(vsEndScript); PushContainer; LContainerTemplate := CurrentContainer; AddEndStripStmt(LContainerTemplate, LStripAction); - RuleStmts(LContainerTemplate, [vsEND]); - Match(vsEND); LEndStripAction := Match(vsEndScript); PopContainer; - result := TProcessTemplateStmt.Create(LSymbol.Position, LContainerTemplate, false); result := AddStripStmt(result, LEndStripAction, sdRight); end; @@ -817,19 +838,15 @@ function TTemplateParser.RuleIncludeStmt: IStmt; LSymbol := FLookahead; Match(vsInclude); Match(vsOpenRoundBracket); - LIncludeExpr := RuleExpression; - LValueSeparator := GetValueSeparatorSymbol; if FLookahead.Token = LValueSeparator then begin Match(LValueSeparator); LScopeExpr := RuleExpression; end; - MatchClosingBracket(vsCloseRoundBracket); LStripAction := Match(vsEndScript); - if LScopeExpr <> nil then begin LContainerTemplate := TTemplate.Create(); @@ -878,11 +895,22 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl function AddPrintStmt: IStmt; var LText: string; + LMatch: TTemplateSymbol; + LExpr: IExpr; begin - LText := MatchValue(vsText); + LMatch := vsText; + LText := MatchValues([vsText, vsNewLine, vsWhiteSpace], LMatch); if LText = '' then exit(nil); - exit(RulePrintStmtVariable(TValueExpr.Create(LSymbol.Position, LText))); + case LMatch of + vsText: + LExpr := TValueExpr.Create(LSymbol.Position, LText); + vsNewLine: + LExpr := TNewLineExpr.Create(LSymbol.Position, LText); + vsWhiteSpace: + LExpr := TWhitespaceExpr.Create(LSymbol.Position, LText); + end; + exit(RulePrintStmtVariable(LExpr)); end; procedure SkipStmt; @@ -904,7 +932,7 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl end; LStmt := nil; case LSymbol.Token of - vsText: + vsNewLine, vsText: begin if poStripWS in FOptions then SkipStmt @@ -934,25 +962,23 @@ function TTemplateParser.RuleTemplateStmt: IStmt; LOptions: IPreserveValue; LContainer: ITemplate; LStripAction: TStripAction; - LContainerStripAction: TStripAction; + LEndStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - + LStripAction := FStripAction; Match(vsTemplate); LExpr := RuleExpression; - LContainerStripAction := Match(vsEndScript); + LEndStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; - AddEndStripStmt(LContainer, LContainerStripAction); RuleStmts(CurrentContainer, [vsEND]); - Match(vsEND); - LStripAction := Match(vsEndScript); + Match(vsEndScript); PopContainer; - result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); - result := AddStripStmt(result, LStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); LContainer.Optimise; end; @@ -961,12 +987,9 @@ function TTemplateParser.RuleSignedFactor: IExpr; LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; - if LSymbol.Token in [vsMinus, vsPLUS] then Match(LSymbol.Token); - result := RuleFactor; - if LSymbol.Token = vsMinus then begin result := RuleFactor; @@ -1025,7 +1048,6 @@ function TTemplateParser.RuleTerm: IExpr; TemplateBinop(LSymbol.Token, LBinOp); Match(LSymbol.Token); LRightExpr := RuleTerm; - if (eoEvalEarly in FContext.Options) and IsValue(result) and IsValue(LRightExpr) then begin case LBinOp of @@ -1041,10 +1063,8 @@ function TTemplateParser.RuleTerm: IExpr; exit(TValueExpr.Create(LSymbol.Position, AsInt(AsValue(result), FContext) mod AsInt(AsValue(LRightExpr), FContext))); end; end; - exit(TBinopExpr.Create(LSymbol.Position, result, LBinOp, LRightExpr)); end; - end; function TTemplateParser.RuleStmt: IStmt; @@ -1055,6 +1075,7 @@ function TTemplateParser.RuleStmt: IStmt; result := nil; LSymbol := FLookahead; LStripAction := Match(vsStartScript); + FStripAction := LStripAction; case FLookahead.Token of vsEndScript: result := RuleNoopStmt; @@ -1082,7 +1103,7 @@ function TTemplateParser.RuleStmt: IStmt; result := RulePrintStmt; vsWhile: result := RuleWhileStmt; - vswith: + vsWith: result := RuleWithStmt; vsRequire: result := RuleRequireStmt; @@ -1175,27 +1196,23 @@ function TTemplateParser.RuleBlockStmt: IStmt; LOptions: IPreserveValue; LContainer: ITemplate; LStripAction: TStripAction; - LContainerStripAction: TStripAction; + LEndStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); - LSymbol := FLookahead; - + LStripAction := FStripAction; Match(vsBlock); LName := RuleExpression; - LContainerStripAction := Match(vsEndScript); - + LEndStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; - AddEndStripStmt(LContainer, LContainerStripAction); RuleStmts(LContainer, [vsEND]); - Match(vsEND); - LStripAction := Match(vsEndScript); - + Match(vsEndScript); PopContainer; result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); - result := AddStripStmt(result, LStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); LContainer.Optimise; end; @@ -1278,41 +1295,28 @@ function TTemplateParser.RuleElIfStmt: IStmt; LFalseContainer: ITemplate; LOptions: IPreserveValue; LSymbol: ITemplateSymbol; - LTrueStripAction: TStripAction; - LFalseStripAction: TStripAction; - LStripAction: TStripAction; begin LSymbol := FLookahead; if not(poAllowElIf in FOptions) then RaiseError(LSymbol.Position, SElIfExpected); - LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poHasElse, poAllowEnd]); - - LStripAction := Match(vsELIF); - + Match(vsELIF); LConditionExpr := RuleExpression; - - LTrueStripAction := Match(vsEndScript); + Match(vsEndScript); // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; - AddEndStripStmt(LTrueContainer, LTrueStripAction); RuleStmts(LTrueContainer, IF_ELIF_END); PopContainer; - if FLookahead.Token = vsElse then begin Match(vsElse); - LFalseStripAction := Match(vsEndScript); - + Match(vsEndScript); PushContainer; LFalseContainer := self.CurrentContainer; - AddEndStripStmt(LFalseContainer, LFalseStripAction); RuleStmts(LFalseContainer, [vsEND, vsELIF]); - PopContainer; end; - if (eoEvalEarly in FContext.Options) and IsValue(LConditionExpr) then begin if AsBoolean(AsValue(LConditionExpr)) then @@ -1321,7 +1325,6 @@ function TTemplateParser.RuleElIfStmt: IStmt; exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) end; result := TIfStmt.Create(LSymbol.Position, LConditionExpr, LTrueContainer, LFalseContainer); - result := AddStripStmt(result, LStripAction, sdRight); end; function TTemplateParser.RuleEndStmt: IStmt; @@ -1463,7 +1466,7 @@ function TTemplateParser.RuleForStmt: IStmt; LOnBegin, LOnEnd, LOnLoop, LOnEmpty, LBetweenItem: ITemplate; LPrevSymbol, LBlockSymbol: TTemplateSymbol; i: integer; - LContainerStripAction: TStripAction; + LStripAction: TStripAction; LEndStripAction: TStripAction; procedure ResolveTemplate(const ASymbol: TTemplateSymbol); @@ -1495,6 +1498,7 @@ function TTemplateParser.RuleForStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); + LStripAction := FStripAction; Match(vsFor); LId := MatchValue(vsID); if FLookahead.Token in [vsIn, vsOf] then @@ -1538,7 +1542,7 @@ function TTemplateParser.RuleForStmt: IStmt; end; end; - LContainerStripAction := Match(vsEndScript); + LEndStripAction := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1546,11 +1550,10 @@ function TTemplateParser.RuleForStmt: IStmt; if (i > 1) and (i mod 2 = 0) then begin Match(LBlockSymbol); - LContainerStripAction := Match(vsEndScript); + Match(vsEndScript); end; PushContainer; LContainerTemplate := CurrentContainer; - AddEndStripStmt(LContainerTemplate, LContainerStripAction); LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); PopContainer; if (i mod 2 = 0) then @@ -1565,14 +1568,13 @@ function TTemplateParser.RuleForStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - - LEndStripAction := Match(vsEndScript); - + Match(vsEndScript); if LForOp in [TForOp.foIn, TForOp.foOf] then result := TForInStmt.Create(LSymbol.Position, LId, LForOp, LRangeExpr, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem) else result := TForRangeStmt.Create(LSymbol.Position, LId, LForOp, LLowValueExpr, LHighValueExpr, LStep, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - result := AddStripStmt(result, LEndStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; @@ -1620,7 +1622,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; LOnBegin, LOnEnd, LOnLoop, LOnEmpty, LBetweenItem: ITemplate; LPrevSymbol, LBlockSymbol: TTemplateSymbol; i: integer; - LContainerStripAction: TStripAction; + LStripAction: TStripAction; LEndStripAction: TStripAction; procedure ResolveTemplate(const ASymbol: TTemplateSymbol); begin @@ -1651,10 +1653,9 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); - + LStripAction := FStripAction; Match(vsWhile); LCondition := RuleExpression; - while FLookahead.Token in [vsOffset, vsLimit] do begin case FLookahead.Token of @@ -1670,8 +1671,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; end; end; end; - - LContainerStripAction := Match(vsEndScript); + LEndStripAction := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1679,11 +1679,10 @@ function TTemplateParser.RuleWhileStmt: IStmt; if (i > 1) and (i mod 2 = 0) then begin Match(LBlockSymbol); - LContainerStripAction := Match(vsEndScript); + Match(vsEndScript); end; PushContainer; LContainerTemplate := CurrentContainer; - AddEndStripStmt(LContainerTemplate, LContainerStripAction); LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); PopContainer; if (i mod 2 = 0) then @@ -1698,13 +1697,13 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - LEndStripAction := Match(vsEndScript); - + Match(vsEndScript); if (eoEvalEarly in FContext.Options) and IsValue(LCondition) and not AsBoolean(AsValue(LCondition)) then result := nil else result := TWhileStmt.Create(LSymbol.Position, LCondition, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - result := AddStripStmt(result, LEndStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; @@ -1714,28 +1713,24 @@ function TTemplateParser.RuleWithStmt: IStmt; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LContainerStripAction: TStripAction; + LStripAction: TStripAction; LEndStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); - LSymbol := FLookahead; - - Match(vswith); + LStripAction := FStripAction; + Match(vsWith); LExpr := RuleExpression; - LContainerStripAction := Match(vsEndScript); - + LEndStripAction := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; - AddEndStripStmt(LContainer, LContainerStripAction); RuleStmts(LContainer, [vsEND]); - Match(vsEND); - LEndStripAction := Match(vsEndScript); - + Match(vsEndScript); PopContainer; result := TWithStmt.Create(LSymbol.Position, LExpr, LContainer); - result := AddStripStmt(result, LEndStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); LContainer.Optimise; end; @@ -1802,36 +1797,28 @@ function TTemplateParser.RuleExtendsStmt: IStmt; LOptions: IPreserveValue; LContainer: ITemplate; LContainerTemplate: TTemplate; + LStripAction: TStripAction; LEndStripAction: TStripAction; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); - LSymbol := FLookahead; - + LStripAction := FStripAction; Match(vsExtends); Match(vsOpenRoundBracket); LName := RuleExpression; - if FLookahead.Token = vsComma then begin Match(vsComma); LScopeExpr := RuleExpression; end; - Match(vsCloseRoundBracket); - - Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block - + LEndStripAction := Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block PushContainer; LContainer := CurrentContainer; - RuleStmts(LContainer, [vsEND]); - Match(vsEND); - LEndStripAction := Match(vsEndScript); - + Match(vsEndScript); PopContainer; - if LScopeExpr <> nil then begin LContainerTemplate := TTemplate.Create(); @@ -1842,7 +1829,8 @@ function TTemplateParser.RuleExtendsStmt: IStmt; begin result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); end; - result := AddStripStmt(result, LEndStripAction, sdRight); + if LStripAction <> saNone then + result := AddStripStmt(result, saNone, sdEnd); LContainer.Optimise; end; @@ -1905,6 +1893,21 @@ function TTemplateParser.MatchNumber(const ASymbol: TTemplateSymbol): extended; exit(StrToFloat(MatchValue(ASymbol), FContext.FormatSettings)); end; +function TTemplateParser.MatchValues(const ASymbols: TTemplateSymbolSet; out ASymbol: TTemplateSymbol): string; +var + LSymbol: ITemplateSymbol; +begin + LSymbol := FLookahead; + if FLookahead.Token in ASymbols then + begin + ASymbol := FLookahead.Token; + result := lookaheadValue; + FLookahead := FLexer.GetToken; + exit; + end; + RaiseError(LSymbol.Position, format(SParsingErrorExpecting, [TemplateSymbolToString(ASymbol)])); +end; + function TTemplateParser.MatchValue(const ASymbol: TTemplateSymbol): string; var LSymbol: ITemplateSymbol; @@ -2078,6 +2081,11 @@ function TIfStmt.GetTrueContainer: ITemplate; exit(FTrueContainer); end; +function TIfStmt.GetHasEnd: boolean; +begin + exit(true); +end; + { TBinopExpr } procedure TBinopExpr.Accept(const AVisitor: ITemplateVisitor); @@ -2561,6 +2569,11 @@ function TDefineTemplateStmt.GetName: IExpr; exit(FName); end; +function TDefineTemplateStmt.GetHasEnd: boolean; +begin + exit(true); +end; + { TWithStmt } procedure TWithStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2579,6 +2592,11 @@ function TWithStmt.GetExpr: IExpr; exit(FExpr); end; +function TWithStmt.GetHasEnd: boolean; +begin + exit(true); +end; + { TIfExpr } procedure TTernaryExpr.Accept(const AVisitor: ITemplateVisitor); @@ -2764,6 +2782,11 @@ function TLoopStmt.GetOnEndContainer: ITemplate; exit(FOnEnd); end; +function TLoopStmt.GetHasEnd: boolean; +begin + exit(true); +end; + function TLoopStmt.GetOnBeginContainer: ITemplate; begin exit(FOnBegin); @@ -2820,11 +2843,31 @@ function TStripStmt.GetAction: TStripAction; exit(FAction); end; +function TStripStmt.GetHasEnd: boolean; +begin + exit(FHasEnd); +end; + function TStripStmt.GetDirection: TStripDirection; begin exit(FDirection); end; +function TStripStmt.GetIndent: string; +begin + exit(FIndent); +end; + +procedure TStripStmt.SetHasEnd(const AHasEnd: boolean); +begin + FHasEnd := AHasEnd; +end; + +procedure TStripStmt.SetIndent(const AIndent: string); +begin + FIndent := AIndent; +end; + { TBlockStmt } procedure TBlockStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2849,6 +2892,11 @@ function TBlockStmt.GetName: IExpr; exit(FName); end; +function TBlockStmt.GetHasEnd: boolean; +begin + exit(true); +end; + function TBlockStmt.NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; begin exit(AEvalVisitor.EvalExprAsString(FName)); @@ -2878,6 +2926,11 @@ function TExtendsStmt.GetName: IExpr; exit(FName); end; +function TExtendsStmt.GetHasEnd: boolean; +begin + exit(true); +end; + function TExtendsStmt.NameAsString(const AEvalVisitor: IEvaluationTemplateVisitor): string; begin exit(AEvalVisitor.EvalExprAsString(FName)); @@ -2896,6 +2949,11 @@ function TAbstractStmt.Flatten: TArray; result[0] := self; end; +function TAbstractStmt.GetHasEnd: boolean; +begin + exit(false); +end; + { TNoopStmt } procedure TNoopStmt.Accept(const AVisitor: ITemplateVisitor); @@ -2903,6 +2961,30 @@ procedure TNoopStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; +{ TNewLineExpr } + +procedure TNewLineExpr.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +constructor TNewLineExpr.Create(const APosition: IPosition; const AValue: TValue); +begin + inherited Create(APosition, AValue); +end; + +{ TWhitespaceExpr } + +procedure TWhitespaceExpr.Accept(const AVisitor: ITemplateVisitor); +begin + AVisitor.Visit(self); +end; + +constructor TWhitespaceExpr.Create(const APosition: IPosition; const AValue: TValue); +begin + inherited Create(APosition, AValue); +end; + initialization initOps; diff --git a/src/Sempare.Template.Visitor.pas b/src/Sempare.Template.Visitor.pas index 552bf0f..7050984 100644 --- a/src/Sempare.Template.Visitor.pas +++ b/src/Sempare.Template.Visitor.pas @@ -51,6 +51,8 @@ TBaseTemplateVisitor = class(TInterfacedObject, ITemplateVisitor) procedure Visit(const AExpr: IVariableExpr); overload; virtual; procedure Visit(const AExpr: IVariableDerefExpr); overload; virtual; procedure Visit(const AExpr: IValueExpr); overload; virtual; + procedure Visit(const AExpr: INewLineExpr); overload; virtual; + procedure Visit(const AExpr: IWhitespaceExpr); overload; virtual; procedure Visit(const AExprList: IExprList); overload; virtual; procedure Visit(const AExpr: ITernaryExpr); overload; virtual; procedure Visit(const AExpr: IArrayExpr); overload; virtual; @@ -316,6 +318,16 @@ procedure TBaseTemplateVisitor.Visit(const AStmt: IExtendsStmt); AcceptVisitor(AStmt.BlockContainer, self); end; +procedure TBaseTemplateVisitor.Visit(const AExpr: IWhitespaceExpr); +begin + Visit(AExpr as IValueExpr); +end; + +procedure TBaseTemplateVisitor.Visit(const AExpr: INewLineExpr); +begin + Visit(AExpr as IValueExpr); +end; + procedure TBaseTemplateVisitor.Visit(const AStmt: INoopStmt); begin diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index dcaf649..a54ccbd 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -135,7 +135,7 @@ procedure TTestTemplateInclude.TestIncludeData; ctx.Template['test2'] := Template.parse('test <% value %>'); // illustrate simple template - Assert.AreEqual('test 123', Template.Eval(ctx, 'test <%_%>', '123')); + Assert.AreEqual('test 123', Template.Eval(ctx, 'test <% _ %>', '123')); // test using include - stackframe is preserved Assert.AreEqual('test 123', Template.Eval(ctx, '<%include (''test'') %>', '123')); diff --git a/tests/Sempare.Template.TestJson.pas b/tests/Sempare.Template.TestJson.pas index 5f565a8..4c56a63 100644 --- a/tests/Sempare.Template.TestJson.pas +++ b/tests/Sempare.Template.TestJson.pas @@ -66,7 +66,7 @@ procedure TTestTemplateJson.TestJson; o2 := TJSonObject.create; o2.AddPair('subval', 'value'); o.AddPair('object', o2); - Assert.AreEqual('string true 123 value', Template.Eval('<% _.str %> <% _.bool%> <%_.null%> <%_.num%> <% _.object.subval %>', o)); + Assert.AreEqual('string true 123 value', Template.Eval('<% _.str %> <% _.bool%> <% _.null%> <% _.num%> <% _.object.subval %>', o)); o.Free; end; From f30fcfe25f75e94aedb5b87ebea40377d85e0670 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 09:57:21 +0100 Subject: [PATCH 091/138] Update form to include ScriptTag and Flatten/Optimise checkbox options --- .../Sempare.Template.DemoForm.dfm | 89 ++++++++++++++----- .../Sempare.Template.DemoForm.pas | 61 ++++++++++++- 2 files changed, 128 insertions(+), 22 deletions(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm index a3d88da..6334e48 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm @@ -3,20 +3,18 @@ object FormRealTime: TFormRealTime Top = 0 Caption = 'Sempare Template Demo' ClientHeight = 487 - ClientWidth = 991 + ClientWidth = 1176 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] - OldCreateOrder = False OnCreate = FormCreate OnResize = FormResize DesignSize = ( - 991 + 1176 487) - PixelsPerInch = 96 TextHeight = 13 object Image1: TImage Left = 24 @@ -303,11 +301,13 @@ object FormRealTime: TFormRealTime object Panel1: TPanel Left = 18 Top = 224 - Width = 951 + Width = 1136 Height = 255 Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Panel1' TabOrder = 4 + ExplicitWidth = 1122 + ExplicitHeight = 222 object Splitter1: TSplitter Left = 465 Top = 1 @@ -324,18 +324,20 @@ object FormRealTime: TFormRealTime ActivePage = tsTemplate Align = alLeft TabOrder = 0 + ExplicitHeight = 220 object tsTemplate: TTabSheet Caption = 'Template' object memoTemplate: TMemo Left = 0 Top = 0 - Width = 456 - Height = 225 + Width = 448 + Height = 212 Align = alClient ScrollBars = ssBoth TabOrder = 0 WantTabs = True OnChange = memoTemplateChange + ExplicitHeight = 179 end end object tsPrettyPrint: TTabSheet @@ -344,8 +346,8 @@ object FormRealTime: TFormRealTime object memoPrettyPrint: TMemo Left = 0 Top = 0 - Width = 456 - Height = 225 + Width = 448 + Height = 212 Align = alClient Lines.Strings = ( 'memoPrettyPrint') @@ -358,18 +360,20 @@ object FormRealTime: TFormRealTime object pcOutput: TPageControl Left = 468 Top = 1 - Width = 482 + Width = 667 Height = 253 ActivePage = tsWebBrowser Align = alClient TabOrder = 1 + ExplicitWidth = 653 + ExplicitHeight = 220 object tsOutput: TTabSheet Caption = 'Output' object memoOutput: TMemo Left = 0 Top = 0 - Width = 474 - Height = 225 + Width = 651 + Height = 212 Align = alClient Lines.Strings = ( 'Memo1') @@ -384,16 +388,14 @@ object FormRealTime: TFormRealTime object WebBrowser1: TWebBrowser Left = 0 Top = 0 - Width = 474 - Height = 225 + Width = 651 + Height = 212 Align = alClient TabOrder = 0 - ExplicitLeft = 1 - ExplicitTop = -92 - ExplicitWidth = 433 - ExplicitHeight = 265 + ExplicitWidth = 637 + ExplicitHeight = 179 ControlData = { - 4C000000FD300000411700000000000000000000000000000000000000000000 + 4C000000A4210000F50A00000000000000000000000000000000000000000000 000000004C000000000000000000000001000000E0D057007335CF11AE690800 2B2E126208000000000000004C0000000114020000000000C000000000000046 8000000000000000000000000000000000000000000000000000000000000000 @@ -405,10 +407,12 @@ object FormRealTime: TFormRealTime object gbOptions: TGroupBox Left = 483 Top = 49 - Width = 385 + Width = 670 Height = 145 + Anchors = [akLeft, akTop, akRight] Caption = 'Context Options' TabOrder = 5 + ExplicitWidth = 656 object cbConvertTabsToSpaces: TCheckBox Left = 18 Top = 23 @@ -522,6 +526,51 @@ object FormRealTime: TFormRealTime TabOrder = 11 OnClick = cbConvertTabsToSpacesClick end + object cmbCustomScriptTags: TComboBox + Left = 400 + Top = 39 + Width = 145 + Height = 21 + Style = csDropDownList + ItemIndex = 0 + TabOrder = 12 + Text = '<% %>' + OnChange = cbUseCustomScriptTagsClick + Items.Strings = ( + '<% %>' + '{{ }}' + '<+ +>' + '{+ +}' + '{% %}' + '<< >>') + end + object cbOptimiseTemplate: TCheckBox + Left = 386 + Top = 92 + Width = 97 + Height = 17 + Caption = 'Optimise Template' + TabOrder = 13 + OnClick = cbOptimiseTemplateClick + end + object cbUseCustomScriptTags: TCheckBox + Left = 384 + Top = 24 + Width = 97 + Height = 17 + Caption = 'Script Tags' + TabOrder = 14 + OnClick = cbUseCustomScriptTagsClick + end + object cbFlattenTemplate: TCheckBox + Left = 384 + Top = 72 + Width = 97 + Height = 17 + Caption = 'cbFlattenTemplate' + TabOrder = 15 + OnClick = cbFlattenTemplateClick + end end object GroupBox1: TGroupBox Left = 18 diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.pas b/demo/VelocityDemo/Sempare.Template.DemoForm.pas index d2360f0..8c89e48 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.pas +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.pas @@ -95,6 +95,10 @@ TFormRealTime = class(TForm) GroupBox1: TGroupBox; butEval: TButton; cbAutoEvaluate: TCheckBox; + cmbCustomScriptTags: TComboBox; + cbOptimiseTemplate: TCheckBox; + cbUseCustomScriptTags: TCheckBox; + cbFlattenTemplate: TCheckBox; procedure cbConvertTabsToSpacesClick(Sender: TObject); procedure cbStripRecurringSpacesClick(Sender: TObject); procedure cbTrimLinesClick(Sender: TObject); @@ -115,6 +119,9 @@ TFormRealTime = class(TForm) procedure butSaveAsClick(Sender: TObject); procedure FormResize(Sender: TObject); procedure butEvalClick(Sender: TObject); + procedure cbUseCustomScriptTagsClick(Sender: TObject); + procedure cbOptimiseTemplateClick(Sender: TObject); + procedure cbFlattenTemplateClick(Sender: TObject); private { Private declarations } FEncoding: TEncoding; @@ -233,6 +240,11 @@ procedure TFormRealTime.cbEvalVarsEarlyClick(Sender: TObject); SetOption(cbEvalVarsEarly.Checked, eoEvalVarsEarly); end; +procedure TFormRealTime.cbFlattenTemplateClick(Sender: TObject); +begin + SetOption(cbFlattenTemplate.Checked, eoFlattenTemplate); +end; + function DefaultEncoder(const AValue: string): string; begin exit(AValue); @@ -247,6 +259,11 @@ procedure TFormRealTime.cbHtmlClick(Sender: TObject); Process; end; +procedure TFormRealTime.cbOptimiseTemplateClick(Sender: TObject); +begin + SetOption(cbOptimiseTemplate.Checked, eoOptimiseTemplate); +end; + procedure TFormRealTime.cbRaiseErrorWhenVariableNotFoundClick(Sender: TObject); begin SetOption(cbRaiseErrorWhenVariableNotFound.Checked, eoRaiseErrorWhenVariableNotFound); @@ -267,6 +284,44 @@ procedure TFormRealTime.cbTrimLinesClick(Sender: TObject); SetOption(cbTrimLines.Checked, eoTrimLines); end; +procedure TFormRealTime.cbUseCustomScriptTagsClick(Sender: TObject); +begin + cbUseCustomScriptTags.Checked := true; + + case cmbCustomScriptTags.ItemIndex of + 1: + begin + FContext.StartToken := '{{'; + FContext.EndToken := '}}'; + end; + 2: + begin + FContext.StartToken := '<+'; + FContext.EndToken := '+>'; + end; + 3: + begin + FContext.StartToken := '{+'; + FContext.EndToken := '+}'; + end; + 4: + begin + FContext.StartToken := '{%'; + FContext.EndToken := '%}'; + end; + 5: + begin + FContext.StartToken := '<<'; + FContext.EndToken := '>>'; + end; + else + begin + FContext.StartToken := '<%'; + FContext.EndToken := '%>'; + end; + end; +end; + procedure TFormRealTime.cbUseHtmlBRClick(Sender: TObject); begin if cbUseHtmlBR.Checked then @@ -301,7 +356,7 @@ procedure TFormRealTime.FormCreate(Sender: TObject); properties.Cells[0, 1] := 'name'; properties.Cells[1, 1] := 'world'; FEncoding := TEncoding.UTF8WithoutBOM; - FTemplate := Template.Parse(''); + FTemplate := Template.Parse(FContext, ''); properties.Cells[0, 0] := 'Variable'; properties.Cells[1, 0] := 'Value'; memoOutput.Lines.Text := ''; @@ -313,6 +368,8 @@ procedure TFormRealTime.FormCreate(Sender: TObject); {$ENDIF} cbHtml.Checked := true; cbUseHtmlBR.Checked := true; + cbFlattenTemplate.Checked := true; + cbOptimiseTemplate.Checked := true; WebBrowser1.Enabled := true; pcTemplate.ActivePageIndex := 0; pcOutput.ActivePageIndex := 0; @@ -405,7 +462,7 @@ procedure TFormRealTime.Process; if not Finit then exit; GridPropsToContext; - LPrettyOk := false; + LPrettyOk := false; try memoPrettyPrint.Lines.Text := Sempare.Template.Template.PrettyPrint(FTemplate); LPrettyOk := true; From 982b59393be864214f9100af47ae69808b79b99e Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 10:12:37 +0100 Subject: [PATCH 092/138] Fix typo on form --- demo/VelocityDemo/Sempare.Template.DemoForm.dfm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm index 6334e48..b4d379b 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm @@ -567,7 +567,7 @@ object FormRealTime: TFormRealTime Top = 72 Width = 97 Height = 17 - Caption = 'cbFlattenTemplate' + Caption = 'Flatten Template' TabOrder = 15 OnClick = cbFlattenTemplateClick end From 3661e166f43c64bf7886910f549a3f0dd62fd1f0 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 10:16:30 +0100 Subject: [PATCH 093/138] work in progress --- src/Sempare.Template.AST.pas | 53 +- src/Sempare.Template.Context.pas | 8 +- src/Sempare.Template.Evaluate.pas | 243 +------ src/Sempare.Template.Lexer.pas | 90 +-- src/Sempare.Template.OptimiseVisitor.pas | 76 ++ src/Sempare.Template.Parser.pas | 708 ++++++++++++++----- src/Sempare.Template.PrettyPrint.pas | 2 +- src/Sempare.Template.TemplateRegistry.pas | 12 +- src/Sempare.Template.pas | 5 +- tests/Sempare.Template.TestNewLineOption.pas | 128 ++-- 10 files changed, 793 insertions(+), 532 deletions(-) create mode 100644 src/Sempare.Template.OptimiseVisitor.pas diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index e236a86..2e9b58c 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -55,12 +55,12 @@ ETemplate = class(Exception); TStripAction = ( // saWhitespace, // - // saUnindent, // - saWhitespaceAndNL, // - saWhitespaceAndNLButOne, // - saNone // + saNL, // + saKeepOneSpace // ); + TStripActionSet = set of TStripAction; + TTemplateSymbol = ( // // general parsing vsInvalid, // @@ -171,12 +171,12 @@ ETemplate = class(Exception); ['{3EC6C60C-164F-4BF5-AF2E-8F3CFC30C594}'] function GetPosition: IPosition; function GetToken: TTemplateSymbol; - function GetStripAction: TStripAction; + function GetStripActions: TStripActionSet; procedure SetToken(const AToken: TTemplateSymbol); function StripWS: boolean; property Token: TTemplateSymbol read GetToken write SetToken; property Position: IPosition read GetPosition; - property StripAction: TStripAction read GetStripAction; + property StripActions: TStripActionSet read GetStripActions; end; ITemplateVisitor = interface; @@ -216,15 +216,18 @@ ETemplate = class(Exception); function GetItem(const AOffset: integer): IStmt; function GetCount: integer; function GetLastItem: IStmt; - procedure Optimise; + procedure FlattenTemplate; + procedure OptimiseTemplate(); property Items[const AOffset: integer]: IStmt read GetItem; property Count: integer read GetCount; property LastItem: IStmt read GetLastItem; end; + TAddLocation = (alLast, alBeforeNL, alAfterNL); + ITemplateAdd = interface(ITemplate) ['{64465D68-0E9D-479F-9EF3-A30E75967809}'] - procedure Add(const AItem: IStmt); + procedure Add(const AItem: IStmt; const AAddLocation: TAddLocation = alLast); end; IBlockStmt = interface; @@ -251,18 +254,18 @@ ETemplate = class(Exception); ['{FB4CC3AB-BFEC-4189-B555-153DDA490D15}'] end; - TStripDirection = (sdLeft, sdRight, sdEnd, sdBeforeNewLine = sdRight, sdAfterNewLine = sdLeft); + TStripDirection = (sdEnd, sdLeft, sdRight, sdBeforeNewLine, sdAfterNewLine); IStripStmt = interface(IStmt) ['{3313745B-D635-4453-9808-660DC462E15C}'] function GetDirection: TStripDirection; - function GetAction: TStripAction; + function GetAction: TStripActionSet; function GetHasEnd: boolean; procedure SetHasEnd(const AHasEnd: boolean); function GetIndent: string; procedure SetIndent(const AIndent: string); property Direction: TStripDirection read GetDirection; - property Action: TStripAction read GetAction; + property Action: TStripActionSet read GetAction; property HasEnd: boolean read GetHasEnd write SetHasEnd; property Indent: string read GetIndent write SetIndent; end; @@ -537,6 +540,7 @@ ETemplate = class(Exception); procedure Visit(const AExpr: IBinopExpr); overload; procedure Visit(const AExpr: IUnaryExpr); overload; procedure Visit(const AExpr: IVariableExpr); overload; + procedure Visit(const AExpr: IWhitespaceExpr); overload; procedure Visit(const AExpr: INewLineExpr); overload; procedure Visit(const AExpr: IValueExpr); overload; procedure Visit(const AExpr: ITernaryExpr); overload; @@ -582,16 +586,33 @@ ETemplate = class(Exception); end; const - StripDirectionStr: array [TStripDirection] of string = ('sdAfterNewLine', 'sdBeforeNewLine', 'sdEnd'); + StripDirectionStr: array [TStripDirection] of string = ( // + 'sdEnd', 'sdLeft', 'sdRight', 'sdBeforeNewLine', 'sdAfterNewLine'); StripActionStr: array [TStripAction] of string = ( // 'saWhitespace', // - //'saUnindent', // - 'saWhitespaceAndNL', // - 'saWhitespaceAndNLButOne', // - 'saNone' // + 'saNL', // + 'saKeepOneSpace' // ); +type + TStringActionsHelper = record helper for TStripActionSet + function ToString: string; + end; + implementation +function TStringActionsHelper.ToString: string; +var + LAction: TStripAction; +begin + result := ''; + for LAction in self do + begin + if result.Length > 0 then + result := result + ','; + result := result + StripActionStr[LAction]; + end; +end; + end. diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index 9249b43..a1eff44 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -77,7 +77,9 @@ interface eoRaiseErrorWhenVariableNotFound, // eoAllowIgnoreNL, // eoStripEmptyLines, // - eoInternalUseNewLine // + eoInternalUseNewLine, // + eoFlattenTemplate, // + eoOptimiseTemplate // ); TTemplateEvaluationOptions = set of TTemplateEvaluationOption; @@ -590,6 +592,8 @@ procedure TTemplateContext.SetNewLine(const ANewLine: string); procedure TTemplateContext.SetOptions(const AOptions: TTemplateEvaluationOptions); begin FOptions := AOptions; + if eoOptimiseTemplate in FOptions then + include(FOptions, eoFlattenTemplate); end; procedure TTemplateContext.SetPrettyPrintOutput(const APrettyPrintOutput: TPrettyPrintOutput); @@ -764,7 +768,7 @@ initialization GDefaultEncoding := TEncoding.UTF8WithoutBOM; GStreamWriterProvider := function(const AStream: TStream; AContext: ITemplateContext): TStreamWriter begin - exit(TNewLineStreamWriter.Create(AStream, AContext.Encoding, AContext.NewLine, AContext.Options)); + exit(TStreamWriter.Create(AStream, AContext.Encoding, 4096)); end; GPrettyPrintOutput := procedure(const APrettyPrint: string) diff --git a/src/Sempare.Template.Evaluate.pas b/src/Sempare.Template.Evaluate.pas index 075517d..ede5f23 100644 --- a/src/Sempare.Template.Evaluate.pas +++ b/src/Sempare.Template.Evaluate.pas @@ -54,29 +54,6 @@ ETemplateEval = class(ETemplate); TLoopOption = (coContinue, coBreak); TLoopOptions = set of TLoopOption; - TNewLineStreamWriter = class(TStreamWriter) - private - FOptions: TTemplateEvaluationOptions; - FNL: string; - FBuffer: TStringBuilder; - FIgnoreNewline: boolean; - FStartOfLine: boolean; - FLastChar: char; - FTrimLines: boolean; - FStripRecurringNL: boolean; - FRemoveEmpty: boolean; - FLastNL: boolean; - FStripStmt: IStripStmt; - procedure TrimEndOfLine; - procedure TrimLast; - procedure DoWrite(); - public - constructor Create(const AStream: TStream; const AEncoding: TEncoding; const ANL: string; const AOptions: TTemplateEvaluationOptions); - destructor Destroy; override; - procedure Write(const AString: string); override; - procedure Handle(const AStmt: IStripStmt); - property IgnoreNewLine: boolean read FIgnoreNewline write FIgnoreNewline; - end; TEvaluationTemplateVisitor = class(TBaseTemplateVisitor, IEvaluationTemplateVisitor) private @@ -85,7 +62,6 @@ TEvaluationTemplateVisitor = class(TBaseTemplateVisitor, IEvaluationTemplateVi FEvalStack: TStack; FStream: TStream; FStreamWriter: TStreamWriter; - FIsNLStreamWriter: boolean; FLoopOptions: TLoopOptions; FContext: ITemplateContext; FEvaluationContext: ITemplateEvaluationContext; @@ -674,8 +650,6 @@ constructor TEvaluationTemplateVisitor.Create(const AContext: ITemplateContext; FStream := AStream; FStreamWriter := FContext.StreamWriterProvider(FStream, FContext); - FIsNLStreamWriter := FStreamWriter is TNewLineStreamWriter; - FEvalStack := TStack.Create; FStackFrames := TObjectStack.Create; FStackFrames.push(AStackFrame); @@ -911,17 +885,7 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IPrintStmt); begin if HasBreakOrContinue then exit; - if supports(AStmt.Expr, INewlineExpr) then - begin - // FStreamWriter.Write('[beforenl]'); - end; FStreamWriter.Write(EvalExprAsString(AStmt.Expr)); - if supports(AStmt.Expr, INewlineExpr) then - begin - // FStreamWriter.Write('[afternl]'); - - end; - end; function CastArg(const AValue: TValue; const AType: TRttiType; const AContext: ITemplateContext): TValue; @@ -1088,25 +1052,8 @@ procedure TEvaluationTemplateVisitor.Visit(const AExpr: IEncodeExpr); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IProcessTemplateStmt); -var - LPrevNewLineState: boolean; - LSWriter: TNewLineStreamWriter; begin - if FIsNLStreamWriter then - begin - LSWriter := TNewLineStreamWriter(FStreamWriter); - LPrevNewLineState := LSWriter.IgnoreNewLine; - try - LSWriter.IgnoreNewLine := not AStmt.AllowNewLine; - AcceptVisitor(AStmt.Container, self); - finally - LSWriter.IgnoreNewLine := LPrevNewLineState; - end; - end - else - begin - AcceptVisitor(AStmt.Container, self); - end; + AcceptVisitor(AStmt.Container, self); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDefineTemplateStmt); @@ -1185,14 +1132,8 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IDebugStmt); end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: IStripStmt); -var - LSWriter: TNewLineStreamWriter; begin - if FIsNLStreamWriter then - begin - LSWriter := TNewLineStreamWriter(FStreamWriter); - LSWriter.Handle(AStmt); - end; + // do nothing end; procedure TEvaluationTemplateVisitor.Visit(const AStmt: ICompositeStmt); @@ -1264,184 +1205,4 @@ procedure TEvaluationTemplateVisitor.Visit(const AStmt: IExtendsStmt); LBlocks.Free; end; end; - -{ TNewLineStreamWriter } - -constructor TNewLineStreamWriter.Create(const AStream: TStream; const AEncoding: TEncoding; const ANL: string; const AOptions: TTemplateEvaluationOptions); -begin - inherited Create(AStream, AEncoding); - FBuffer := TStringBuilder.Create; - FOptions := AOptions; - FNL := ANL; - FStartOfLine := true; - FLastChar := #0; - FTrimLines := eoTrimLines in FOptions; - FRemoveEmpty := eoStripEmptyLines in FOptions; - FStripRecurringNL := eoStripRecurringNewlines in FOptions; - FLastNL := true; -end; - -destructor TNewLineStreamWriter.Destroy; -begin - TrimEndOfLine(); - if FBuffer.length > 0 then - DoWrite; - FBuffer.Free; - inherited; -end; - -procedure TNewLineStreamWriter.DoWrite(); -begin - inherited write(FBuffer.ToString()); - FBuffer.clear; -end; -{$WARN WIDECHAR_REDUCED OFF} - -const - STRIP_CHARS: array [TStripAction] of set of char = ( // - [' ', #9], // saWhitespace - // [' ', #9], // saUnindent - [' ', #9, #13, #10], // saWhitespaceAndNL - [' ', #9, #13, #10], // saWhitespaceAndNLButOne - [] // saNone - ); -{$WARN WIDECHAR_REDUCED ON} - -procedure TNewLineStreamWriter.Handle(const AStmt: IStripStmt); -var - LStripped: boolean; -begin - if AStmt.Action = saNone then - begin - FStripStmt := nil; - exit; - end; - if AStmt.Direction = sdLeft then - begin - LStripped := false; - while (FBuffer.length > 0) and charinset(FBuffer.Chars[FBuffer.length - 1], STRIP_CHARS[AStmt.Action]) do - begin - FBuffer.length := FBuffer.length - 1; - LStripped := true; - end; - if LStripped and (AStmt.Action = saWhitespaceAndNLButOne) then - begin - FBuffer.Append(' '); - end; - end - else - begin - FStripStmt := AStmt; - end; -end; - -procedure TNewLineStreamWriter.TrimEndOfLine; -begin - if eoTrimLines in FOptions then - begin - while (FBuffer.length >= 1) and IsWhitespace(FBuffer.Chars[FBuffer.length - 1]) do - FBuffer.length := FBuffer.length - 1; - end; -end; - -procedure TNewLineStreamWriter.TrimLast; -begin - if (FBuffer.length >= 1) then - begin - FBuffer.length := FBuffer.length - 1; - if FBuffer.length = 0 then - FLastChar := #0 - else - FLastChar := FBuffer.Chars[FBuffer.length - 1]; - FStartOfLine := FBuffer.length = 0; - end; -end; - -procedure TNewLineStreamWriter.Write(const AString: string); -var - LChar: char; - LIdx: integer; - LHasLooped: boolean; - LProcessed: integer; - LStripAction: TStripAction; -begin - LIdx := 1; - if assigned(FStripStmt) then - begin - LStripAction := FStripStmt.Action; - LHasLooped := false; - LProcessed := 0; - while LIdx <= length(AString) do - begin - LChar := AString[LIdx]; - if charinset(LChar, STRIP_CHARS[LStripAction]) then - begin - inc(LIdx); - LHasLooped := true; - if LChar = #10 then - begin - FStripStmt := nil; - break; - end; - continue; - end - else - begin - FStripStmt := nil; - break; - end; - inc(LProcessed); - end; - if LHasLooped then - begin - if assigned(FStripStmt) and (LProcessed = length(AString)) then - exit; - if LStripAction = saWhitespaceAndNLButOne then - begin - FBuffer.Append(' '); - end; - end; - end; - for LIdx := LIdx to length(AString) do - begin - LChar := AString[LIdx]; - if IsWhitespace(LChar) and FStartOfLine and FTrimLines then - begin - FLastChar := LChar; - continue; - end; - if IsNewline(LChar) then - begin - if IsCR(FLastChar) then - begin - TrimLast; - end; - if FTrimLines then - begin - TrimEndOfLine; - end; - - if not FIgnoreNewline then - begin - if not FStripRecurringNL or not IsNewline(FLastChar) then - begin - if not FRemoveEmpty or not FLastNL then - begin - FBuffer.Append(FNL); - FLastNL := true; - end; - FStartOfLine := true; - end; - end; - end - else - begin - FBuffer.Append(LChar); - FLastNL := false; - FStartOfLine := false; - end; - FLastChar := LChar; - end; -end; - end. diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index cef14e0..e588b07 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -91,7 +91,7 @@ TPair = record class constructor Create; overload; private FReader: TStreamReader; - FNextToken: ITemplateSymbol; + FNextToken: TQueue; FStream: TStream; FLine: integer; FPos: integer; @@ -127,14 +127,14 @@ TSimpleTemplateSymbol = class(TInterfacedObject, ITemplateSymbol) FToken: TTemplateSymbol; FPosition: IPosition; FStripWS: Boolean; - FStripAction: TStripAction; + FStripActions: TStripActionSet; function GetPosition: IPosition; public - constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone); + constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []); procedure SetToken(const AToken: TTemplateSymbol); function GetToken: TTemplateSymbol; function StripWS: Boolean; - function GetStripAction: TStripAction; + function GetStripActions: TStripActionSet; end; TTemplateValueSymbol = class(TSimpleTemplateSymbol, ITemplateValueSymbol) @@ -165,6 +165,7 @@ constructor TTemplateLexer.Create(const AContext: ITemplateContext; const AStrea FReader := TStreamReader.Create(AStream, AContext.Encoding, false, 4096); FPrevLineOffset := -1; FLineOffset := 0; + FNextToken := TQueue.Create; FOptions := AContext.Options; FStartScript := AContext.StartToken; FEndScript := AContext.EndToken; @@ -193,6 +194,7 @@ constructor TTemplateLexer.Create(const AContext: ITemplateContext; const AStrea destructor TTemplateLexer.Destroy; begin + FNextToken.Free; FAccumulator.Free; FReader.Free; if FManageStream then @@ -258,12 +260,12 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone; const AGetInput: Boolean = True): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []; const AGetInput: Boolean = True): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripAction); + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripActions); if AGetInput then GetInput; end; @@ -308,9 +310,9 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(ValueToken(vsString)); end; - function isEndOfScript(const ALastChar: char; out aResult: ITemplateSymbol; const AStripAction: TStripAction): Boolean; + function isEndOfScript(const ALastChar: char; out aResult: ITemplateSymbol; const AStripActions: TStripActionSet): Boolean; - function CheckEnd(const ACurrent, ANext: char; const AStripAction: TStripAction; const AGetInput: Boolean = false): Boolean; + function CheckEnd(const ACurrent, ANext: char; const AStripActions: TStripActionSet; const AGetInput: Boolean = false): Boolean; begin if CharInSet(ACurrent, [FEndScript[1], FEndStripScript[1]]) then begin @@ -321,16 +323,16 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; if ANext = LEndExpect then begin LEndStripWS := ACurrent = FEndStripScript[1]; - if (AStripAction = saNone) or AGetInput then + if (AStripActions = []) or AGetInput then GetInput; if FAccumulator.length > 0 then begin aResult := ValueToken(vsText); - FNextToken := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + FNextToken.enqueue(SimpleToken(VsEndScript, LEndStripWS, AStripActions)); end else begin - aResult := SimpleToken(VsEndScript, LEndStripWS, AStripAction); + aResult := SimpleToken(VsEndScript, LEndStripWS, AStripActions); end; FState := SText; exit(True); @@ -342,16 +344,16 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; var LGetInput: Boolean; begin - LGetInput := AStripAction <> saNone; + LGetInput := AStripActions <> []; if LGetInput then begin GetInput; - Result := CheckEnd(ALastChar, FCurrent.Input, AStripAction); + Result := CheckEnd(ALastChar, FCurrent.Input, AStripActions); if Result then exit; - exit(CheckEnd(FCurrent.Input, FLookahead.Input, AStripAction, True)); + exit(CheckEnd(FCurrent.Input, FLookahead.Input, AStripActions, True)); end; - exit(CheckEnd(FCurrent.Input, FLookahead.Input, saNone)); + exit(CheckEnd(FCurrent.Input, FLookahead.Input, [])); end; begin @@ -434,24 +436,24 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(SimpleToken(vsQUESTION)); '+': begin - if isEndOfScript('+', Result, TStripAction.saWhitespaceAndNLButOne) then + if isEndOfScript('+', Result, [saWhitespace, saNL, saKeepOneSpace]) then exit else - exit(SimpleToken(vsPLUS, false, saNone, false)); + exit(SimpleToken(vsPLUS, false, [], false)); end; '-': begin - if isEndOfScript('-', Result, TStripAction.saWhitespace) then + if isEndOfScript('-', Result, [saWhitespace]) then exit else - exit(SimpleToken(vsMinus, false, saNone, false)); + exit(SimpleToken(vsMinus, false, [], false)); end; '*': begin - if isEndOfScript('*', Result, TStripAction.saWhitespaceAndNL) then + if isEndOfScript('*', Result, [saWhitespace, saNL]) then exit else - exit(SimpleToken(vsMULT, false, saNone, false)); + exit(SimpleToken(vsMULT, false, [], false)); end; '/': exit(SimpleToken(vsSLASH)); @@ -502,7 +504,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(SimpleToken(vsCOLON)); else begin - if isEndOfScript(#0, Result, TStripAction.saNone) then + if isEndOfScript(#0, Result, []) then exit; end; end; @@ -513,7 +515,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; if FAccumulator.length > 0 then begin Result := ValueToken(vsText); - FNextToken := SimpleToken(vsEOF); + FNextToken.enqueue(SimpleToken(vsEOF)); end else exit(SimpleToken(vsEOF)); @@ -524,7 +526,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; LLine: integer; LPosition: integer; LIsStartStripWSToken: Boolean; - LState: TStripAction; + LState: TStripActionSet; function MakePosition: IPosition; begin @@ -534,12 +536,12 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripAction: TStripAction = saNone): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripAction); + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripActions); GetInput; end; @@ -577,7 +579,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; lstr := AFinal(FAccumulator.ToString); FAccumulator.Clear; FAccumulator.Append(lstr); - FNextToken := ValueToken(vsNewLine, false); + FNextToken.enqueue(ValueToken(AToken, false)); end else begin @@ -585,7 +587,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; lstr := AFinal(FAccumulator.ToString); FAccumulator.Clear; FAccumulator.Append(lstr); - ASymbol := ValueToken(vsNewLine, false); + ASymbol := ValueToken(AToken, false); end; exit(True); end; @@ -606,18 +608,18 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; // '_': // LState := TStripAction.saUnindent; '-': - LState := TStripAction.saWhitespace; + LState := [saWhitespace]; '+': - LState := TStripAction.saWhitespaceAndNLButOne; + LState := [saWhitespace, saNL, saKeepOneSpace]; '*': - LState := TStripAction.saWhitespaceAndNL; + LState := [saWhitespace, saNL]; else - LState := TStripAction.saNone; + LState := []; end; - if LState <> TStripAction.saNone then + if LState <> [] then GetInput; FState := SScript; - FNextToken := SimpleToken(VsStartScript, LIsStartStripWSToken, LState); + FNextToken.enqueue(SimpleToken(VsStartScript, LIsStartStripWSToken, LState)); exit(); end; @@ -675,7 +677,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; if FAccumulator.length > 0 then begin Result := ValueToken(vsText); - FNextToken := SimpleToken(vsEOF); + FNextToken.enqueue(SimpleToken(vsEOF)); end else exit(SimpleToken(vsEOF)); @@ -683,11 +685,9 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; function TTemplateLexer.GetToken: ITemplateSymbol; begin - if FNextToken <> nil then + if FNextToken.count > 0 then begin - Result := FNextToken; - FNextToken := nil; - exit; + exit(FNextToken.dequeue); end; case FState of SText: @@ -704,11 +704,11 @@ procedure TTemplateLexer.SwallowInput; GetInput; end; -{ TSimpleMustacheToken } +{ TSimpleTemplateSymbol } -constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean; const AStripAction: TStripAction); +constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean; const AStripActions: TStripActionSet); begin - FStripAction := AStripAction; + FStripActions := AStripActions; FToken := AToken; FPosition := APosition; FStripWS := AStripWS; @@ -719,9 +719,9 @@ function TSimpleTemplateSymbol.GetPosition: IPosition; exit(FPosition); end; -function TSimpleTemplateSymbol.GetStripAction: TStripAction; +function TSimpleTemplateSymbol.GetStripActions: TStripActionSet; begin - exit(FStripAction); + exit(FStripActions); end; function TSimpleTemplateSymbol.GetToken: TTemplateSymbol; @@ -739,7 +739,7 @@ function TSimpleTemplateSymbol.StripWS: Boolean; exit(FStripWS); end; -{ TStringMustacheToken } +{ TTemplateValueSymbol } constructor TTemplateValueSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AString: string); begin diff --git a/src/Sempare.Template.OptimiseVisitor.pas b/src/Sempare.Template.OptimiseVisitor.pas new file mode 100644 index 0000000..c337673 --- /dev/null +++ b/src/Sempare.Template.OptimiseVisitor.pas @@ -0,0 +1,76 @@ +(*%************************************************************************************************* + * ___ * + * / __| ___ _ __ _ __ __ _ _ _ ___ * + * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * + * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * + * |_| * + **************************************************************************************************** + * * + * Sempare Template Engine * + * * + * * + * https://github.com/sempare/sempare-delphi-template-engine * + **************************************************************************************************** + * * + * Copyright (c) 2019-2023 Sempare Limited * + * * + * Contact: info@sempare.ltd * + * * + * Licensed under the GPL Version 3.0 or the Sempare Commercial License * + * You may not use this file except in compliance with one of these Licenses. * + * You may obtain a copy of the Licenses at * + * * + * https://www.gnu.org/licenses/gpl-3.0.en.html * + * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the Licenses is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + *************************************************************************************************%*) +unit Sempare.Template.OptimiseVisitor; + +interface + +uses + System.Generics.Collections, + Sempare.Template.AST, + Sempare.Template.Common, + Sempare.Template.Visitor; + +type + IOptimiseVisitor = interface(ITemplateVisitor) + ['{98EB0C4D-6AE5-41C4-82AE-FA30DFBE11F5}'] + end; + + TOptimiseVisitor = class(TNoExprTemplateVisitor, IOptimiseVisitor) + private + FFlatten, FOptimise: boolean; + public + constructor Create(const AFlatten, AOptimise: boolean); + + procedure Visit(const ATemplate: ITemplate); overload; override; + end; + +implementation + +{ TOptimiseVisitor } + +constructor TOptimiseVisitor.Create(const AFlatten, AOptimise: boolean); +begin + FFlatten := AFlatten or AOptimise; + FOptimise := AOptimise; +end; + +procedure TOptimiseVisitor.Visit(const ATemplate: ITemplate); +begin + inherited; + if FFlatten then + ATemplate.FlattenTemplate; + if FOptimise then + ATemplate.OptimiseTemplate; +end; + +end. diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 81170e5..873f3f7 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -71,9 +71,11 @@ implementation TTemplate = class(TInterfacedObject, ITemplate, ITemplateAdd, ITemplateVisitorHost) private FPosition: IPosition; - FArray: TArray; - function Flatten: TArray; - procedure Optimise; + FArray: TList; + + // function Flatten: TArray; + procedure FlattenTemplate; + procedure OptimiseTemplate; function GetFilename: string; procedure SetFilename(const AFilename: string); function GetLine: integer; @@ -82,12 +84,18 @@ TTemplate = class(TInterfacedObject, ITemplate, ITemplateAdd, ITemplateVisitor procedure SetPos(const Apos: integer); function GetItem(const AOffset: integer): IStmt; function GetCount: integer; - procedure Add(const AItem: IStmt); + function IsPrintExpr(const AStmt: IStmt; const ASymExpr: TGuid): boolean; + function IsPrintNewlineExpr(const AStmt: IStmt): boolean; + function IsPrintWhitespaceExpr(const AStmt: IStmt): boolean; + function IsStripStmt(const AStmt: IStmt; out AStripStmt: IStripStmt): boolean; + + procedure Add(const AItem: IStmt; const AAddLocation: TAddLocation = alLast); function GetLastItem: IStmt; procedure Accept(const AVisitor: ITemplateVisitor); public - constructor Create(); + constructor Create; + destructor Destroy; override; end; TAbstractBase = class abstract(TInterfacedObject, IPositional, ITemplateVisitorHost) @@ -200,18 +208,18 @@ TPrintStmt = class(TAbstractStmtWithExpr, IPrintStmt) TStripStmt = class(TAbstractStmt, IStripStmt) private FDirection: TStripDirection; - FAction: TStripAction; + FAction: TStripActionSet; FHasEnd: boolean; FIndent: string; function GetIndent: string; procedure SetIndent(const AIndent: string); function GetDirection: TStripDirection; - function GetAction: TStripAction; + function GetAction: TStripActionSet; function GetHasEnd: boolean; override; procedure SetHasEnd(const AHasEnd: boolean); procedure Accept(const AVisitor: ITemplateVisitor); override; public - constructor Create(const ADirection: TStripDirection; const AAction: TStripAction); + constructor Create(const ADirection: TStripDirection; const AAction: TStripActionSet); end; TCompositeStmt = class(TAbstractStmt, ICompositeStmt) @@ -519,6 +527,13 @@ EEndOfBlock = class(ETemplate); TParserOption = (poAllowEnd, poAllowElse, poAllowElIf, poHasElse, poInLoop, poStripWS); TParserOptions = set of TParserOption; + TStripActionStackItem = record + + BeforeNLStripActions: TStripActionSet; + AfterNLStripActions: TStripActionSet; + constructor Create(const ABeforeNLStripActions: TStripActionSet; const AAfterNLStripActions: TStripActionSet); + end; + TTemplateParser = class(TInterfacedObject, ITemplateParser) private FContext: ITemplateContext; @@ -526,23 +541,32 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) FLexer: ITemplateLexer; FContainerStack: TStack; FOptions: TParserOptions; - FStripAction: TStripAction; + + FBeforeNLStripActions: TStripActionSet; + FAfterNLStripActions: TStripActionSet; + FStripActionsStack: TStack; + + function IsNLStmt(const AStmt: IStmt): boolean; + + procedure PushStripAction(const AContainer: ITemplate; const ABeforeNLStripActions: TStripActionSet; const AAfterNLStripActions: TStripActionSet); + procedure PopStripAction; + function PushContainer: ITemplate; function PopContainer: ITemplate; function CurrentContainer: ITemplate; - function lookaheadValue: string; function MatchValues(const ASymbols: TTemplateSymbolSet; out ASymbol: TTemplateSymbol): string; function MatchValue(const ASymbol: TTemplateSymbol): string; procedure Match(ASymbol: ITemplateSymbol); overload; inline; - function Match(const ASymbol: TTemplateSymbol): TStripAction; overload; + function Match(const ASymbol: TTemplateSymbol): TStripActionSet; overload; function MatchNumber(const ASymbol: TTemplateSymbol): extended; procedure MatchClosingBracket(const AExpect: TTemplateSymbol); - function AddStripStmt(const AStmt: IStmt; const AStripAction: TStripAction; const ADirection: TStripDirection): IStmt; - function AddEndStripStmt(const ATemplate: ITemplate; const AStripAction: TStripAction): ITemplate; + function AddStripStmt(const AStmt: IStmt; const AStripActions: TStripActionSet; const ADirection: TStripDirection): IStmt; overload; + function AddStripStmt(const ATemplate: ITemplate; const AStripActions: TStripActionSet; const ADirection: TStripDirection): ITemplate; overload; private function RuleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; function RuleStmt: IStmt; + function RuleIgnoreNewline: IStmt; function RuleCommentStmt: IStmt; function RuleIdStmt: IStmt; @@ -585,7 +609,7 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function Parse(const AStream: TStream; const AManagedStream: boolean): ITemplate; end; -function Flatten(const AStmts: TArray): TArray; +function Flatten(const AStmts: TList): TArray; var LStmt: IStmt; LFlattenedStmts: TList; @@ -610,7 +634,8 @@ procedure Optimise(const ATemplates: TArray); begin if assigned(LTemplate) then begin - LTemplate.Optimise; + LTemplate.FlattenTemplate; + LTemplate.OptimiseTemplate; end; end; end; @@ -702,6 +727,15 @@ function TTemplateParser.GetValueSeparatorSymbol: TTemplateSymbol; exit(vsComma); end; +function TTemplateParser.IsNLStmt(const AStmt: IStmt): boolean; +var + LPrintStmt: IPrintStmt; +begin + if not supports(AStmt, IPrintStmt, LPrintStmt) then + exit(false); + exit(supports(LPrintStmt.Expr, INewLineExpr)); +end; + procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); begin assert(AExpect in [vsCloseRoundBracket, vsCloseSquareBracket]); @@ -710,32 +744,37 @@ procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); Match(AExpect); end; -function TTemplateParser.AddEndStripStmt(const ATemplate: ITemplate; const AStripAction: TStripAction): ITemplate; +function TTemplateParser.AddStripStmt(const ATemplate: ITemplate; const AStripActions: TStripActionSet; const ADirection: TStripDirection): ITemplate; var LAdd: ITemplateAdd; + LStmt: IStripStmt; begin - if AStripAction = saNone then + if AStripActions = [] then exit(ATemplate); result := ATemplate; - supports(result, ITemplateAdd, LAdd); - LAdd.Add(TStripStmt.Create(sdRight, AStripAction)); + LStmt := TStripStmt.Create(ADirection, AStripActions); + + if supports(ATemplate, ITemplateAdd, LAdd) then + LAdd.Add(LStmt); + end; -function TTemplateParser.AddStripStmt(const AStmt: IStmt; const AStripAction: TStripAction; const ADirection: TStripDirection): IStmt; +function TTemplateParser.AddStripStmt(const AStmt: IStmt; const AStripActions: TStripActionSet; const ADirection: TStripDirection): IStmt; var LStripStmt: IStripStmt; begin - if AStripAction = saNone then - exit(AStmt) - else - begin - LStripStmt := TStripStmt.Create(ADirection, AStripAction); - LStripStmt.HasEnd := AStmt.HasEnd; - if ADirection = sdLeft then - begin + if AStripActions = [] then + exit(AStmt); + + LStripStmt := TStripStmt.Create(ADirection, AStripActions); + if AStmt = nil then + exit(LStripStmt); + LStripStmt.HasEnd := AStmt.HasEnd; + + case ADirection of + sdLeft, sdAfterNewLine: exit(TCompositeStmt.Create(LStripStmt, AStmt)); - end - else + sdRight, sdBeforeNewLine: exit(TCompositeStmt.Create(AStmt, LStripStmt)); end; end; @@ -745,6 +784,8 @@ constructor TTemplateParser.Create(AContext: ITemplateContext); FOptions := []; FContext := AContext; FContainerStack := TStack.Create; + FStripActionsStack := TStack.Create; + PushStripAction(nil, [], []); end; const @@ -758,19 +799,31 @@ function TTemplateParser.RuleIfStmt: IStmt; LContainerAdd: ITemplateAdd; LOptions: IPreserveValue; LSymbol: ITemplateSymbol; - LEndStripAction: TStripAction; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poAllowEnd, poAllowElIf]); LSymbol := FLookahead; - LStripAction := FStripAction; + + LAfterNLStripActions := FAfterNLStripActions; Match(vsIF); LConditionalExpr := RuleExpression; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); + // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; + + PushStripAction(LTrueContainer, LBeforeNLStripActions, LAfterNLStripActions); + + // AddStripStmt(LTrueContainer, LAfterNLStripActions, sdAfterNewLine); + RuleStmts(LTrueContainer, IF_ELIF_END); + + PopStripAction(); + + // AddStripStmt(LTrueContainer, LAfterNLStripActions, sdLeft); + PopContainer; PushContainer; LFalseContainer := self.CurrentContainer; @@ -786,7 +839,18 @@ function TTemplateParser.RuleIfStmt: IStmt; begin Match(vsElse); Match(vsEndScript); + + PushStripAction(LFalseContainer, LBeforeNLStripActions, LAfterNLStripActions); + + + // AddStripStmt(LFalseContainer, LAfterNLStripActions, sdAfterNewLine); + RuleStmts(LFalseContainer, [vsEND]); + + PopStripAction(); + + // AddStripStmt(LFalseContainer, LBeforeNLStripActions, sdBeforeNewLine); + end; PopContainer; Match(vsEND); @@ -799,8 +863,8 @@ function TTemplateParser.RuleIfStmt: IStmt; exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) end; result := TIfStmt.Create(LSymbol.Position, LConditionalExpr, LTrueContainer, LFalseContainer); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleIgnoreNewline: IStmt; @@ -808,22 +872,30 @@ function TTemplateParser.RuleIgnoreNewline: IStmt; LSymbol: ITemplateSymbol; LContainerTemplate: ITemplate; LOptions: IPreserveValue; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsIgnoreNL); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); + PushContainer; LContainerTemplate := CurrentContainer; - AddEndStripStmt(LContainerTemplate, LStripAction); + + PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LContainerTemplate, [vsEND]); + + AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); + Match(vsEND); - LEndStripAction := Match(vsEndScript); + Match(vsEndScript); PopContainer; result := TProcessTemplateStmt.Create(LSymbol.Position, LContainerTemplate, false); - result := AddStripStmt(result, LEndStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleIncludeStmt: IStmt; @@ -833,9 +905,11 @@ function TTemplateParser.RuleIncludeStmt: IStmt; LScopeExpr: IExpr; LContainerTemplate: TTemplate; LValueSeparator: TTemplateSymbol; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsInclude); Match(vsOpenRoundBracket); LIncludeExpr := RuleExpression; @@ -846,7 +920,7 @@ function TTemplateParser.RuleIncludeStmt: IStmt; LScopeExpr := RuleExpression; end; MatchClosingBracket(vsCloseRoundBracket); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); if LScopeExpr <> nil then begin LContainerTemplate := TTemplate.Create(); @@ -857,13 +931,14 @@ function TTemplateParser.RuleIncludeStmt: IStmt; begin result := TIncludeStmt.Create(LSymbol.Position, LIncludeExpr); end; - result := AddStripStmt(result, LStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleRequireStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; + LStripAction: TStripActionSet; begin LSymbol := FLookahead; Match(vsRequire); @@ -871,7 +946,7 @@ function TTemplateParser.RuleRequireStmt: IStmt; result := TRequireStmt.Create(LSymbol.Position, self.RuleExprList()); MatchClosingBracket(vsCloseRoundBracket); LStripAction := Match(vsEndScript); - result := AddStripStmt(result, LStripAction, sdRight); + // result := AddStripStmt(result, LStripAction, sdBeforeNewLine); end; function TTemplateParser.RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; @@ -891,6 +966,7 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl LSymbol: ITemplateSymbol; LLoop: boolean; LEndToken: TTemplateSymbolSet; + LIsNLStmt: boolean; function AddPrintStmt: IStmt; var @@ -932,7 +1008,7 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl end; LStmt := nil; case LSymbol.Token of - vsNewLine, vsText: + vsWhiteSpace, vsNewLine, vsText: begin if poStripWS in FOptions then SkipStmt @@ -950,9 +1026,18 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl end; if (LStmt <> nil) and not supports(LStmt, IElseStmt) then begin + LIsNLStmt := IsNLStmt(LStmt); + if LIsNLStmt then + AddStripStmt(LParentContainer, FStripActionsStack.peek.BeforeNLStripActions, sdBeforeNewLine); LParentContainer.Add(LStmt); + if LIsNLStmt then + AddStripStmt(LParentContainer, FStripActionsStack.peek.AfterNLStripActions, sdAfterNewLine); end; end; + if vsEND in AEndToken then + begin + PopStripAction; + end; end; function TTemplateParser.RuleTemplateStmt: IStmt; @@ -961,25 +1046,32 @@ function TTemplateParser.RuleTemplateStmt: IStmt; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - LStripAction := FStripAction; + LAfterNLStripActions := FAfterNLStripActions; Match(vsTemplate); LExpr := RuleExpression; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; + + PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(CurrentContainer, [vsEND]); + + AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); + Match(vsEND); Match(vsEndScript); PopContainer; result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); - LContainer.Optimise; + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + LContainer.FlattenTemplate; + LContainer.OptimiseTemplate; end; function TTemplateParser.RuleSignedFactor: IExpr; @@ -1070,12 +1162,10 @@ function TTemplateParser.RuleTerm: IExpr; function TTemplateParser.RuleStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; begin result := nil; LSymbol := FLookahead; - LStripAction := Match(vsStartScript); - FStripAction := LStripAction; + FAfterNLStripActions := Match(vsStartScript); case FLookahead.Token of vsEndScript: result := RuleNoopStmt; @@ -1122,7 +1212,6 @@ function TTemplateParser.RuleStmt: IStmt; begin result := TDebugStmt.Create(result); end; - result := AddStripStmt(result, LStripAction, sdLeft); end; function TTemplateParser.RuleVariable: IExpr; @@ -1195,97 +1284,115 @@ function TTemplateParser.RuleBlockStmt: IStmt; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - LStripAction := FStripAction; + LAfterNLStripActions := FAfterNLStripActions; Match(vsBlock); LName := RuleExpression; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; + + PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LContainer, [vsEND]); + + AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); + Match(vsEND); Match(vsEndScript); PopContainer; result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); - LContainer.Optimise; + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + LContainer.FlattenTemplate; + LContainer.OptimiseTemplate; end; function TTemplateParser.RuleNoopStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; + LStripAction: TStripActionSet; begin LSymbol := FLookahead; LStripAction := Match(vsEndScript); result := TNoopStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LStripAction, sdRight); + // result := AddStripStmt(result, LStripAction, sdBeforeNewLine); end; function TTemplateParser.RuleBreakStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsBreak); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); result := TBreakStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleCommentStmt: IStmt; var LSymbol: ITemplateSymbol; - LStartStripAction: TStripAction; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; - LStartStripAction := Match(vsComment); - LStripAction := Match(vsEndScript); + LAfterNLStripActions := FAfterNLStripActions; + Match(vsComment); + LBeforeNLStripActions := Match(vsEndScript); result := TCommentStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LStartStripAction, sdLeft); - result := AddStripStmt(result, LStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleContinueStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsContinue); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); result := TContinueStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleCycleStmt: IStmt; var LSymbol: ITemplateSymbol; LListExpr: IExprList; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; + Match(vsCycle); Match(vsOpenRoundBracket); LListExpr := RuleExprList(); MatchClosingBracket(vsCloseRoundBracket); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); result := TCycleStmt.Create(LSymbol.Position, LListExpr); - result := AddStripStmt(result, LStripAction, sdRight); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleElIfStmt: IStmt; @@ -1295,10 +1402,16 @@ function TTemplateParser.RuleElIfStmt: IStmt; LFalseContainer: ITemplate; LOptions: IPreserveValue; LSymbol: ITemplateSymbol; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; if not(poAllowElIf in FOptions) then RaiseError(LSymbol.Position, SElIfExpected); + + LBeforeNLStripActions := FStripActionsStack.peek.BeforeNLStripActions; + LAfterNLStripActions := FStripActionsStack.peek.AfterNLStripActions; + LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poHasElse, poAllowEnd]); Match(vsELIF); LConditionExpr := RuleExpression; @@ -1306,7 +1419,14 @@ function TTemplateParser.RuleElIfStmt: IStmt; // create new container for true condition PushContainer; LTrueContainer := self.CurrentContainer; + + PushStripAction(LTrueContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LTrueContainer, IF_ELIF_END); + PopStripAction; + + AddStripStmt(LTrueContainer, LBeforeNLStripActions, sdBeforeNewLine); + PopContainer; if FLookahead.Token = vsElse then begin @@ -1314,7 +1434,13 @@ function TTemplateParser.RuleElIfStmt: IStmt; Match(vsEndScript); PushContainer; LFalseContainer := self.CurrentContainer; + + PushStripAction(LFalseContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LFalseContainer, [vsEND, vsELIF]); + + AddStripStmt(LFalseContainer, LBeforeNLStripActions, sdBeforeNewLine); + PopContainer; end; if (eoEvalEarly in FContext.Options) and IsValue(LConditionExpr) then @@ -1325,6 +1451,8 @@ function TTemplateParser.RuleElIfStmt: IStmt; exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) end; result := TIfStmt.Create(LSymbol.Position, LConditionExpr, LTrueContainer, LFalseContainer); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleEndStmt: IStmt; @@ -1466,8 +1594,8 @@ function TTemplateParser.RuleForStmt: IStmt; LOnBegin, LOnEnd, LOnLoop, LOnEmpty, LBetweenItem: ITemplate; LPrevSymbol, LBlockSymbol: TTemplateSymbol; i: integer; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; procedure ResolveTemplate(const ASymbol: TTemplateSymbol); begin @@ -1498,7 +1626,7 @@ function TTemplateParser.RuleForStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); - LStripAction := FStripAction; + LAfterNLStripActions := FAfterNLStripActions; Match(vsFor); LId := MatchValue(vsID); if FLookahead.Token in [vsIn, vsOf] then @@ -1542,7 +1670,7 @@ function TTemplateParser.RuleForStmt: IStmt; end; end; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1554,7 +1682,13 @@ function TTemplateParser.RuleForStmt: IStmt; end; PushContainer; LContainerTemplate := CurrentContainer; + + PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); + + AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); + PopContainer; if (i mod 2 = 0) then begin @@ -1573,8 +1707,8 @@ function TTemplateParser.RuleForStmt: IStmt; result := TForInStmt.Create(LSymbol.Position, LId, LForOp, LRangeExpr, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem) else result := TForRangeStmt.Create(LSymbol.Position, LId, LForOp, LLowValueExpr, LHighValueExpr, LStep, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; @@ -1595,21 +1729,29 @@ function TTemplateParser.RuleIdStmt: IStmt; var LSymbol: ITemplateSymbol; LExpr: IExpr; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; LExpr := RuleVariable; + LAfterNLStripActions := FAfterNLStripActions; if FLookahead.Token = vsCOLONEQ then begin result := RuleAssignStmt(LExpr); end - else + else if FLookahead.Token = vsEndScript then begin LExpr := TEncodeExpr.Create(LSymbol.Position, LExpr); result := RulePrintStmtVariable(LExpr); + end + else + begin + RaiseError(LSymbol.Position, format(SParsingErrorExpecting, ['variable reference, function call or assignment'])); + end; - LStripAction := Match(vsEndScript); - result := AddStripStmt(result, LStripAction, sdRight); + LBeforeNLStripActions := Match(vsEndScript); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleWhileStmt: IStmt; @@ -1622,8 +1764,8 @@ function TTemplateParser.RuleWhileStmt: IStmt; LOnBegin, LOnEnd, LOnLoop, LOnEmpty, LBetweenItem: ITemplate; LPrevSymbol, LBlockSymbol: TTemplateSymbol; i: integer; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; procedure ResolveTemplate(const ASymbol: TTemplateSymbol); begin case ASymbol of @@ -1653,7 +1795,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); - LStripAction := FStripAction; + LAfterNLStripActions := FAfterNLStripActions; Match(vsWhile); LCondition := RuleExpression; while FLookahead.Token in [vsOffset, vsLimit] do @@ -1671,7 +1813,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; end; end; end; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1683,7 +1825,13 @@ function TTemplateParser.RuleWhileStmt: IStmt; end; PushContainer; LContainerTemplate := CurrentContainer; + + PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); + + AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); + PopContainer; if (i mod 2 = 0) then begin @@ -1702,8 +1850,8 @@ function TTemplateParser.RuleWhileStmt: IStmt; result := nil else result := TWhileStmt.Create(LSymbol.Position, LCondition, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); end; @@ -1713,41 +1861,53 @@ function TTemplateParser.RuleWithStmt: IStmt; LSymbol: ITemplateSymbol; LOptions: IPreserveValue; LContainer: ITemplate; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - LStripAction := FStripAction; + LAfterNLStripActions := FAfterNLStripActions; Match(vsWith); LExpr := RuleExpression; - LEndStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); PushContainer; LContainer := CurrentContainer; + + PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LContainer, [vsEND]); + + AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); + Match(vsEND); Match(vsEndScript); PopContainer; result := TWithStmt.Create(LSymbol.Position, LExpr, LContainer); - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); - LContainer.Optimise; + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + LContainer.FlattenTemplate; + LContainer.OptimiseTemplate; end; function TTemplateParser.RulePrintStmt: IStmt; var LSymbol: ITemplateSymbol; LExpr: IExpr; - LStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsPrint); Match(vsOpenRoundBracket); LExpr := RuleExpression; + MatchClosingBracket(vsCloseRoundBracket); - LStripAction := Match(vsEndScript); + LBeforeNLStripActions := Match(vsEndScript); result := TPrintStmt.Create(LSymbol.Position, LExpr); - result := AddStripStmt(result, LStripAction, sdRight); + + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RulePrintStmtVariable(AExpr: IExpr): IStmt; @@ -1781,12 +1941,19 @@ function TTemplateParser.RuleExprList(const AEndToken: TTemplateSymbol): IExprLi function TTemplateParser.RuleExprStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripAction; + LExpr: IExpr; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; - result := RulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, RuleExpression)); - LStripAction := Match(vsEndScript); - result := AddStripStmt(result, LStripAction, sdRight); + LAfterNLStripActions := FAfterNLStripActions; + + LExpr := RuleExpression; + result := RulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, LExpr)); + LBeforeNLStripActions := Match(vsEndScript); + + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleExtendsStmt: IStmt; @@ -1797,12 +1964,14 @@ function TTemplateParser.RuleExtendsStmt: IStmt; LOptions: IPreserveValue; LContainer: ITemplate; LContainerTemplate: TTemplate; - LStripAction: TStripAction; - LEndStripAction: TStripAction; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; - LStripAction := FStripAction; + + LAfterNLStripActions := FAfterNLStripActions; + Match(vsExtends); Match(vsOpenRoundBracket); LName := RuleExpression; @@ -1812,10 +1981,16 @@ function TTemplateParser.RuleExtendsStmt: IStmt; LScopeExpr := RuleExpression; end; Match(vsCloseRoundBracket); - LEndStripAction := Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block + LBeforeNLStripActions := Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block PushContainer; LContainer := CurrentContainer; + + PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + RuleStmts(LContainer, [vsEND]); + + AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); + Match(vsEND); Match(vsEndScript); PopContainer; @@ -1829,21 +2004,23 @@ function TTemplateParser.RuleExtendsStmt: IStmt; begin result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); end; - if LStripAction <> saNone then - result := AddStripStmt(result, saNone, sdEnd); - LContainer.Optimise; + result := AddStripStmt(result, LBeforeNLStripActions, sdRight); + result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + LContainer.FlattenTemplate; + LContainer.OptimiseTemplate; end; function TTemplateParser.CurrentContainer: ITemplate; begin if FContainerStack.Count <> 0 then - exit(FContainerStack.Peek) + exit(FContainerStack.peek) else exit(nil); end; destructor TTemplateParser.Destroy; begin + FStripActionsStack.Free; FContainerStack.Free; inherited; end; @@ -1856,12 +2033,12 @@ function TTemplateParser.lookaheadValue: string; exit(val.Value) end; -function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripAction; +function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripActionSet; var LSymbol: ITemplateSymbol; begin LSymbol := FLookahead; - result := TStripAction.saNone; + result := []; if ASymbol = FLookahead.Token then begin if LSymbol.StripWS then @@ -1874,8 +2051,16 @@ function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripAction; end; end; case ASymbol of - vsStartScript, vsEndScript: - result := LSymbol.StripAction; + vsStartScript: + begin + result := LSymbol.StripActions; + FAfterNLStripActions := result; + end; + vsEndScript: + begin + result := LSymbol.StripActions; + FBeforeNLStripActions := result; + end; end; FLookahead := FLexer.GetToken; exit; @@ -1935,7 +2120,10 @@ function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boo RuleStmts(result, []); end; Match(vsEOF); - result.Optimise; + if eoFlattenTemplate in FContext.Options then + result.FlattenTemplate; + if eoOptimiseTemplate in FContext.Options then + result.OptimiseTemplate; if eoPrettyPrint in FContext.Options then FContext.PrettyPrintOutput(Template.PrettyPrint(result)); end; @@ -1943,13 +2131,48 @@ function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boo function TTemplateParser.PopContainer: ITemplate; begin result := CurrentContainer; - FContainerStack.Pop; + FContainerStack.pop; +end; + +procedure TTemplateParser.PopStripAction; +begin + FStripActionsStack.pop; end; function TTemplateParser.PushContainer: ITemplate; begin result := CurrentContainer; - FContainerStack.Push(TTemplate.Create()); + FContainerStack.push(TTemplate.Create()); +end; + +procedure TTemplateParser.PushStripAction(const AContainer: ITemplate; const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet); +var + LTop: TStripActionStackItem; + LBeforeNLStripActions, LAfterNLStripActions: TStripActionSet; +begin + if FStripActionsStack.Count = 0 then + begin + LBeforeNLStripActions := ABeforeNLStripActions; + LAfterNLStripActions := AAfterNLStripActions; + end + else + begin + LTop := FStripActionsStack.peek; + + LBeforeNLStripActions := ABeforeNLStripActions; + LAfterNLStripActions := AAfterNLStripActions; + + if LBeforeNLStripActions = [] then + LBeforeNLStripActions := LTop.BeforeNLStripActions; + + if LAfterNLStripActions = [] then + LAfterNLStripActions := LTop.AfterNLStripActions; + end; + FStripActionsStack.push(TStripActionStackItem.Create(LBeforeNLStripActions, LAfterNLStripActions)); + + if assigned(AContainer) then + AddStripStmt(AContainer, LAfterNLStripActions, sdAfterNewLine); + end; { TValueExpr } @@ -2244,28 +2467,48 @@ procedure TTemplate.Accept(const AVisitor: ITemplateVisitor); end; end; -procedure TTemplate.Add(const AItem: IStmt); +procedure TTemplate.Add(const AItem: IStmt; const AAddLocation: TAddLocation); var + i: integer; LOffset: integer; begin - LOffset := length(FArray); - setlength(FArray, LOffset + 1); - FArray[LOffset] := AItem; + LOffset := 0; + if AAddLocation in [alAfterNL, alBeforeNL] then + begin + if AAddLocation = alAfterNL then + LOffset := 1; + for i := 0 to FArray.Count - 1 do + begin + if IsPrintNewlineExpr(FArray[i]) then + begin + FArray.insert(i + LOffset, AItem); + exit; + end; + end; + end; + FArray.Add(AItem); end; constructor TTemplate.Create; begin FPosition := TPosition.Create('', 1, 1); + FArray := TList.Create; end; -function TTemplate.Flatten: TArray; +destructor TTemplate.Destroy; begin - result := Sempare.Template.Parser.Flatten(FArray); + FArray.Free; + inherited; end; +{ + function TTemplate.Flatten: TArray; + begin + exit(Sempare.Template.Parser.Flatten(FArray)); + end; } function TTemplate.GetCount: integer; begin - exit(length(FArray)); + exit(FArray.Count); end; function TTemplate.GetFilename: string; @@ -2291,31 +2534,166 @@ function TTemplate.GetPos: integer; exit(FPosition.pos); end; -procedure TTemplate.Optimise; +function TTemplate.IsPrintExpr(const AStmt: IStmt; const ASymExpr: TGuid): boolean; +var + LPrintStmt: IPrintStmt; +begin + if not supports(AStmt, IPrintStmt, LPrintStmt) then + exit(false); - function Strip(const AArray: TArray): TArray; - var - LStmt: IStmt; - LStmts: TList; + exit(supports(LPrintStmt.Expr, ASymExpr)); +end; + +function TTemplate.IsPrintNewlineExpr(const AStmt: IStmt): boolean; +begin + exit(IsPrintExpr(AStmt, INewLineExpr)); +end; + +function TTemplate.IsPrintWhitespaceExpr(const AStmt: IStmt): boolean; +begin + exit(IsPrintExpr(AStmt, IWhitespaceExpr)); +end; + +function TTemplate.IsStripStmt(const AStmt: IStmt; out AStripStmt: IStripStmt): boolean; +begin + exit(supports(AStmt, IStripStmt, AStripStmt)); +end; + +function IsAny(const AStmt: IStmt; const AGuids: array of TGuid): boolean; +var + LGuid: TGuid; +begin + for LGuid in AGuids do begin - LStmts := TList.Create; - try - for LStmt in AArray do + if supports(AStmt, LGuid) then + exit(true); + end; + exit(false); +end; + +procedure TTemplate.FlattenTemplate; +var + LStmt: IStmt; + LStmts: TList; + // i: integer; + // j: integer; + // LScanStmt: IStripStmt; + // LReviewStmt: IStmt; + +begin + LStmts := TList.Create; + try + for LStmt in FArray do + begin + if IsAny(LStmt, [IEndStmt, ICommentStmt, IElseStmt, INoopStmt]) then begin - if supports(LStmt, IEndStmt) or supports(LStmt, ICommentStmt) or supports(LStmt, IElseStmt) or supports(LStmt, INoopStmt) then - begin - continue; - end; - LStmts.Add(LStmt); + continue; end; - exit(LStmts.ToArray); - finally - LStmts.Free; + LStmts.AddRange(LStmt.Flatten); + end; + FArray.Clear; + FArray.AddRange(LStmts); + finally + LStmts.Free; + end; +end; + +procedure TTemplate.OptimiseTemplate; +var + LStmt: IStmt; + LStmts: TList; + i: integer; + j: integer; + LScanStmt: IStripStmt; + LReviewStmt: IStmt; + + function CanStrip(const AAction: TStripActionSet; const AStmt: IStmt): boolean; + begin + exit((saNL in AAction) and IsPrintNewlineExpr(AStmt) or // + (saWhitespace in AAction) and IsPrintWhitespaceExpr(AStmt)); + + end; + function KeepSpaceIfRequired(const AAction: TStripActionSet): IStmt; + begin + if saKeepOneSpace in LScanStmt.Action then + begin + exit(TPrintStmt.Create(nil, TWhitespaceExpr.Create(nil, ' '))); + end + else + begin + exit(nil); end; end; begin - FArray := Strip(Flatten); + LStmts := TList.Create; + try + for LStmt in FArray do + begin + if IsAny(LStmt, [IEndStmt, ICommentStmt, IElseStmt, INoopStmt]) then + begin + continue; + end; + LStmts.AddRange(LStmt); + end; + FArray.Clear; + FArray.AddRange(LStmts); + + i := 0; + while i <= FArray.Count - 1 do + begin + LStmt := FArray[i]; + if IsStripStmt(LStmt, LScanStmt) then + begin + case LScanStmt.Direction of + sdBeforeNewLine, sdLeft: + begin + j := i - 1; + while j >= 0 do + begin + LReviewStmt := FArray[j]; + if CanStrip(LScanStmt.Action, LReviewStmt) then + begin + FArray[j] := nil; + end + else + break; + dec(j); + end; + FArray[i] := KeepSpaceIfRequired(LScanStmt.Action); + end; + sdAfterNewLine, sdRight: + begin + j := i + 1; + while j <= FArray.Count - 1 do + begin + LReviewStmt := FArray[j]; + if CanStrip(LScanStmt.Action, LReviewStmt) then + begin + FArray[j] := nil; + end + else + break; + inc(j); + end; + FArray[i] := KeepSpaceIfRequired(LScanStmt.Action); + end; + end; + end; + inc(i); + end; + LStmts.Clear; + for LStmt in FArray do + begin + if LStmt <> nil then + LStmts.Add(LStmt); + end; + FArray.Clear; + FArray.AddRange(LStmts); + finally + LStmts.Free; + end; + FArray.Capacity := FArray.Count; end; procedure TTemplate.SetFilename(const AFilename: string); @@ -2807,12 +3185,16 @@ constructor TCompositeStmt.Create(const AFirstStmt, ASecondStmt: IStmt); function TCompositeStmt.Flatten: TArray; var - LStmts: TArray; + LStmts: TList; begin - setlength(LStmts, 2); - LStmts[0] := FFirstStmt; - LStmts[1] := FSecondStmt; - exit(Sempare.Template.Parser.Flatten(LStmts)); + LStmts := TList.Create; + try + LStmts.Add(FFirstStmt); + LStmts.Add(FSecondStmt); + exit(Sempare.Template.Parser.Flatten(LStmts)); + finally + LStmts.Free; + end; end; function TCompositeStmt.GetFirstStmt: IStmt; @@ -2832,13 +3214,13 @@ procedure TStripStmt.Accept(const AVisitor: ITemplateVisitor); AVisitor.Visit(self); end; -constructor TStripStmt.Create(const ADirection: TStripDirection; const AAction: TStripAction); +constructor TStripStmt.Create(const ADirection: TStripDirection; const AAction: TStripActionSet); begin FDirection := ADirection; FAction := AAction; end; -function TStripStmt.GetAction: TStripAction; +function TStripStmt.GetAction: TStripActionSet; begin exit(FAction); end; @@ -2985,6 +3367,14 @@ constructor TWhitespaceExpr.Create(const APosition: IPosition; const AValue: TVa inherited Create(APosition, AValue); end; +{ TStripActionStackItem } + +constructor TStripActionStackItem.Create(const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet); +begin + BeforeNLStripActions := ABeforeNLStripActions; + AfterNLStripActions := AAfterNLStripActions; +end; + initialization initOps; diff --git a/src/Sempare.Template.PrettyPrint.pas b/src/Sempare.Template.PrettyPrint.pas index 581fd34..523fe49 100644 --- a/src/Sempare.Template.PrettyPrint.pas +++ b/src/Sempare.Template.PrettyPrint.pas @@ -557,7 +557,7 @@ procedure TPrettyPrintTemplateVisitor.Visit(const AStmt: IStripStmt); write('<%% strip('); write(StripDirectionStr[AStmt.Direction]); write(','); - write(StripActionStr[AStmt.Action]); + write(AStmt.Action.ToString); write(')'); writeln('%%>'); end; diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index d43b139..565cab8 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -70,7 +70,8 @@ TAbstractProxyTemplate = class abstract(TInterfacedObject, ITemplate) function GetItem(const AOffset: integer): IStmt; function GetCount: integer; function GetLastItem: IStmt; - procedure Optimise; + procedure FlattenTemplate; + procedure OptimiseTemplate; procedure Accept(const AVisitor: ITemplateVisitor); function GetFilename: string; procedure SetFilename(const AFilename: string); @@ -575,9 +576,14 @@ function TAbstractProxyTemplate.GetPos: integer; exit(FTemplate.pos); end; -procedure TAbstractProxyTemplate.Optimise; +procedure TAbstractProxyTemplate.FlattenTemplate; begin - FTemplate.Optimise; + FTemplate.FlattenTemplate; +end; + +procedure TAbstractProxyTemplate.OptimiseTemplate; +begin + FTemplate.OptimiseTemplate; end; procedure TAbstractProxyTemplate.SetFilename(const AFilename: string); diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index 03ca936..c15451e 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -59,6 +59,9 @@ interface eoRaiseErrorWhenVariableNotFound = TTemplateEvaluationOption.eoRaiseErrorWhenVariableNotFound; eoReplaceNewline = TTemplateEvaluationOption.eoReplaceNewline; eoStripEmptyLines = TTemplateEvaluationOption.eoStripEmptyLines; + eoFlattenTemplate = TTemplateEvaluationOption.eoFlattenTemplate; + eoOptimiseTemplate = TTemplateEvaluationOption.eoOptimiseTemplate; + tlsLoadResource = Sempare.Template.TemplateRegistry.tlsLoadResource; tlsLoadFile = Sempare.Template.TemplateRegistry.tlsLoadFile; tlsLoadCustom = Sempare.Template.TemplateRegistry.tlsLoadCustom; @@ -89,7 +92,7 @@ Template = class {$ENDIF} {$ENDIF} public - class function Context(const AOptions: TTemplateEvaluationOptions = []): ITemplateContext; inline; static; + class function Context(const AOptions: TTemplateEvaluationOptions = [eoOptimiseTemplate]): ITemplateContext; inline; static; class function Parser(const AContext: ITemplateContext): ITemplateParser; overload; inline; static; class function Parser(): ITemplateParser; overload; inline; static; class function PrettyPrint(ATemplate: ITemplate): string; inline; static; diff --git a/tests/Sempare.Template.TestNewLineOption.pas b/tests/Sempare.Template.TestNewLineOption.pas index a4415db..d4daca7 100644 --- a/tests/Sempare.Template.TestNewLineOption.pas +++ b/tests/Sempare.Template.TestNewLineOption.pas @@ -123,124 +123,124 @@ procedure TTestNewLineOption.TestNL; end; procedure TTestNewLineOption.TestRecurringNLAndSpaces; -var - s: TStringStream; - w: TNewLineStreamWriter; - str: string; +// var +// s: TStringStream; +// w: TNewLineStreamWriter; +// str:string; begin - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines, eoStripRecurringNewlines]); - try - w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally - w.Free; - str := s.datastring; - Assert.AreEqual(#10'hello'#10'world'#10, str); - s.Free; - end; + // s := TStringStream.Create; + // w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines, eoStripRecurringNewlines]); + // try + // w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); + // finally + // w.Free; + // str := s.datastring; + // Assert.AreEqual(#10'hello'#10'world'#10, str); + // s.Free; + // end; end; procedure TTestNewLineOption.TestRecurringOnlyNL; -var - s: TStringStream; - w: TNewLineStreamWriter; - str: string; +// var +// s: TStringStream; +// w: TNewLineStreamWriter; +// str: string; begin - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripRecurringNewlines]); - try - w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally - w.Free; - str := s.datastring; - Assert.AreEqual(#10' hello '#10' world '#10, str); - s.Free; - end; + // s := TStringStream.Create; + // w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripRecurringNewlines]); + // try + // w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); + // finally + // w.Free; + // str := s.datastring; + // Assert.AreEqual(#10' hello '#10' world '#10, str); + // s.Free; + // end; end; procedure TTestNewLineOption.TestRecurringSpaces; -var +{ var s: TStringStream; w: TNewLineStreamWriter; - s2: string; + s2: string; } begin - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); - try + { s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); + try w.Write(' '#10#10' '#10#10); - finally + finally w.Free; s2 := s.datastring; Assert.AreEqual(' '#10#10' '#10#10, s2); s.Free; - end; - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); - try + end; + s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); + try w.Write(' '#10#10' '#10#10); - finally + finally w.Free; s2 := s.datastring; Assert.AreEqual(''#10#10''#10#10, s2); s.Free; - end; + end; - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); - try + s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); + try w.Write(' hello '#10#10' world '); - finally + finally w.Free; s2 := s.datastring; Assert.AreEqual(' hello '#10#10' world ', s2); s.Free; - end; - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); - try + end; + s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); + try w.Write(' hello '#10#10' world '); - finally + finally w.Free; s2 := s.datastring; Assert.AreEqual('hello'#10#10'world', s2); s.Free; - end; + end; } end; procedure TTestNewLineOption.RemoveEmptyAndStripLines; -var +{ var s: TStringStream; w: TNewLineStreamWriter; - str: string; + str: string; } begin - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines, eoTrimLines]); - try + { s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines, eoTrimLines]); + try w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally + finally w.Free; str := s.datastring; Assert.AreEqual('hello'#10'world'#10, str); s.Free; - end; + end; } end; procedure TTestNewLineOption.RemoveEmptyLines; -var +{ var s: TStringStream; w: TNewLineStreamWriter; - str: string; + str: string; } begin - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines]); - try + { s := TStringStream.Create; + w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines]); + try w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally + finally w.Free; str := s.datastring; Assert.AreEqual(' hello '#10' world '#10, str); s.Free; - end; + end; } end; procedure TTestNewLineOption.TestNewLine; From 86c5eeb84cc1a9aa6ae9d78235b3f85b8d89c0fc Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:09:05 +0100 Subject: [PATCH 094/138] Update header --- demo/VelocityDemo/Sempare.Template.Demo.dpr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/VelocityDemo/Sempare.Template.Demo.dpr b/demo/VelocityDemo/Sempare.Template.Demo.dpr index a022cac..1cf729a 100644 --- a/demo/VelocityDemo/Sempare.Template.Demo.dpr +++ b/demo/VelocityDemo/Sempare.Template.Demo.dpr @@ -6,7 +6,7 @@ * |_| * **************************************************************************************************** * * - * Sempare Templating Engine * + * Sempare Template Engine * * * * * * https://github.com/sempare/sempare-delphi-template-engine * From 565d1744e6ca78c9aba14e2358381f80d20538d2 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:11:15 +0100 Subject: [PATCH 095/138] Update form to support custom script tags, show whitespace, flatten and optimise --- .../Sempare.Template.DemoForm.dfm | 61 +++++++------ .../Sempare.Template.DemoForm.pas | 91 ++++++++++++------- 2 files changed, 93 insertions(+), 59 deletions(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm index b4d379b..567b68e 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm @@ -306,8 +306,8 @@ object FormRealTime: TFormRealTime Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Panel1' TabOrder = 4 - ExplicitWidth = 1122 - ExplicitHeight = 222 + ExplicitWidth = 1132 + ExplicitHeight = 254 object Splitter1: TSplitter Left = 465 Top = 1 @@ -324,20 +324,20 @@ object FormRealTime: TFormRealTime ActivePage = tsTemplate Align = alLeft TabOrder = 0 - ExplicitHeight = 220 + ExplicitHeight = 252 object tsTemplate: TTabSheet Caption = 'Template' object memoTemplate: TMemo Left = 0 Top = 0 - Width = 448 - Height = 212 + Width = 456 + Height = 225 Align = alClient ScrollBars = ssBoth TabOrder = 0 WantTabs = True OnChange = memoTemplateChange - ExplicitHeight = 179 + ExplicitHeight = 224 end end object tsPrettyPrint: TTabSheet @@ -346,8 +346,8 @@ object FormRealTime: TFormRealTime object memoPrettyPrint: TMemo Left = 0 Top = 0 - Width = 448 - Height = 212 + Width = 456 + Height = 225 Align = alClient Lines.Strings = ( 'memoPrettyPrint') @@ -365,15 +365,15 @@ object FormRealTime: TFormRealTime ActivePage = tsWebBrowser Align = alClient TabOrder = 1 - ExplicitWidth = 653 - ExplicitHeight = 220 + ExplicitWidth = 663 + ExplicitHeight = 252 object tsOutput: TTabSheet Caption = 'Output' object memoOutput: TMemo Left = 0 Top = 0 - Width = 651 - Height = 212 + Width = 659 + Height = 225 Align = alClient Lines.Strings = ( 'Memo1') @@ -388,14 +388,14 @@ object FormRealTime: TFormRealTime object WebBrowser1: TWebBrowser Left = 0 Top = 0 - Width = 651 - Height = 212 + Width = 659 + Height = 225 Align = alClient TabOrder = 0 - ExplicitWidth = 637 - ExplicitHeight = 179 + ExplicitWidth = 655 + ExplicitHeight = 224 ControlData = { - 4C000000A4210000F50A00000000000000000000000000000000000000000000 + 4C0000001C440000411700000000000000000000000000000000000000000000 000000004C000000000000000000000001000000E0D057007335CF11AE690800 2B2E126208000000000000004C0000000114020000000000C000000000000046 8000000000000000000000000000000000000000000000000000000000000000 @@ -412,7 +412,7 @@ object FormRealTime: TFormRealTime Anchors = [akLeft, akTop, akRight] Caption = 'Context Options' TabOrder = 5 - ExplicitWidth = 656 + ExplicitWidth = 666 object cbConvertTabsToSpaces: TCheckBox Left = 18 Top = 23 @@ -527,15 +527,15 @@ object FormRealTime: TFormRealTime OnClick = cbConvertTabsToSpacesClick end object cmbCustomScriptTags: TComboBox - Left = 400 - Top = 39 - Width = 145 + Left = 408 + Top = 45 + Width = 81 Height = 21 Style = csDropDownList ItemIndex = 0 TabOrder = 12 Text = '<% %>' - OnChange = cbUseCustomScriptTagsClick + OnChange = cmbCustomScriptTagsChange Items.Strings = ( '<% %>' '{{ }}' @@ -545,9 +545,9 @@ object FormRealTime: TFormRealTime '<< >>') end object cbOptimiseTemplate: TCheckBox - Left = 386 + Left = 384 Top = 92 - Width = 97 + Width = 159 Height = 17 Caption = 'Optimise Template' TabOrder = 13 @@ -564,13 +564,22 @@ object FormRealTime: TFormRealTime end object cbFlattenTemplate: TCheckBox Left = 384 - Top = 72 - Width = 97 + Top = 69 + Width = 153 Height = 17 Caption = 'Flatten Template' TabOrder = 15 OnClick = cbFlattenTemplateClick end + object cbShowWhitespace: TCheckBox + Left = 384 + Top = 115 + Width = 159 + Height = 17 + Caption = 'Show Whitespace' + TabOrder = 16 + OnClick = cbShowWhitespaceClick + end end object GroupBox1: TGroupBox Left = 18 diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.pas b/demo/VelocityDemo/Sempare.Template.DemoForm.pas index 8c89e48..24ad8f7 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.pas +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.pas @@ -99,6 +99,7 @@ TFormRealTime = class(TForm) cbOptimiseTemplate: TCheckBox; cbUseCustomScriptTags: TCheckBox; cbFlattenTemplate: TCheckBox; + cbShowWhitespace: TCheckBox; procedure cbConvertTabsToSpacesClick(Sender: TObject); procedure cbStripRecurringSpacesClick(Sender: TObject); procedure cbTrimLinesClick(Sender: TObject); @@ -122,6 +123,8 @@ TFormRealTime = class(TForm) procedure cbUseCustomScriptTagsClick(Sender: TObject); procedure cbOptimiseTemplateClick(Sender: TObject); procedure cbFlattenTemplateClick(Sender: TObject); + procedure cmbCustomScriptTagsChange(Sender: TObject); + procedure cbShowWhitespaceClick(Sender: TObject); private { Private declarations } FEncoding: TEncoding; @@ -133,6 +136,8 @@ TFormRealTime = class(TForm) procedure GridPropsToContext; procedure WriteTmpHtml; procedure SetOption(const AEnable: boolean; const AOption: TTemplateEvaluationOption); + procedure SetScriptTags(const AIdx: Integer); + public { Public declarations } procedure OnException(Sender: TObject; E: Exception); @@ -262,6 +267,8 @@ procedure TFormRealTime.cbHtmlClick(Sender: TObject); procedure TFormRealTime.cbOptimiseTemplateClick(Sender: TObject); begin SetOption(cbOptimiseTemplate.Checked, eoOptimiseTemplate); + if cbOptimiseTemplate.Checked then + cbFlattenTemplate.Checked := true; end; procedure TFormRealTime.cbRaiseErrorWhenVariableNotFoundClick(Sender: TObject); @@ -286,40 +293,10 @@ procedure TFormRealTime.cbTrimLinesClick(Sender: TObject); procedure TFormRealTime.cbUseCustomScriptTagsClick(Sender: TObject); begin - cbUseCustomScriptTags.Checked := true; - - case cmbCustomScriptTags.ItemIndex of - 1: - begin - FContext.StartToken := '{{'; - FContext.EndToken := '}}'; - end; - 2: - begin - FContext.StartToken := '<+'; - FContext.EndToken := '+>'; - end; - 3: - begin - FContext.StartToken := '{+'; - FContext.EndToken := '+}'; - end; - 4: - begin - FContext.StartToken := '{%'; - FContext.EndToken := '%}'; - end; - 5: - begin - FContext.StartToken := '<<'; - FContext.EndToken := '>>'; - end; + if cbUseCustomScriptTags.Checked then + SetScriptTags(cmbCustomScriptTags.ItemIndex) else - begin - FContext.StartToken := '<%'; - FContext.EndToken := '%>'; - end; - end; + SetScriptTags(0); end; procedure TFormRealTime.cbUseHtmlBRClick(Sender: TObject); @@ -331,6 +308,13 @@ procedure TFormRealTime.cbUseHtmlBRClick(Sender: TObject); SetOption(cbUseHtmlBR.Checked, eoReplaceNewline); end; +procedure TFormRealTime.cmbCustomScriptTagsChange(Sender: TObject); +begin + cbUseCustomScriptTags.Checked := true; + SetScriptTags(cmbCustomScriptTags.ItemIndex); + Process; +end; + procedure TFormRealTime.cbSetEncodingClick(Sender: TObject); begin if cbSetEncoding.Checked then @@ -349,6 +333,11 @@ procedure TFormRealTime.cbSetEncodingClick(Sender: TObject); Process; end; +procedure TFormRealTime.cbShowWhitespaceClick(Sender: TObject); +begin + SetOption(cbShowWhitespace.Checked, eoShowWhitespace); +end; + procedure TFormRealTime.FormCreate(Sender: TObject); begin FContext := Template.Context(); @@ -499,6 +488,42 @@ procedure TFormRealTime.SetOption(const AEnable: boolean; const AOption: TTempla Process; end; +procedure TFormRealTime.SetScriptTags(const AIdx: Integer); +begin + case AIdx of + 1: + begin + FContext.StartToken := '{{'; + FContext.EndToken := '}}'; + end; + 2: + begin + FContext.StartToken := '<+'; + FContext.EndToken := '+>'; + end; + 3: + begin + FContext.StartToken := '{+'; + FContext.EndToken := '+}'; + end; + 4: + begin + FContext.StartToken := '{%'; + FContext.EndToken := '%}'; + end; + 5: + begin + FContext.StartToken := '<<'; + FContext.EndToken := '>>'; + end; + else + begin + FContext.StartToken := '<%'; + FContext.EndToken := '%>'; + end; + end; +end; + procedure TFormRealTime.WriteTmpHtml; var url: string; From 579e45e0bfaf0d90baa2f616e66924e27daae03d Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:15:12 +0100 Subject: [PATCH 096/138] Update header --- Sempare.Template.Tester.dpr | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Sempare.Template.Tester.dpr b/Sempare.Template.Tester.dpr index 7a89dec..4965e5c 100644 --- a/Sempare.Template.Tester.dpr +++ b/Sempare.Template.Tester.dpr @@ -1,4 +1,4 @@ - (*%************************************************************************************************* +(*%************************************************************************************************* * ___ * * / __| ___ _ __ _ __ __ _ _ _ ___ * * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * @@ -6,7 +6,7 @@ * |_| * **************************************************************************************************** * * - * Sempare Templating Engine * + * Sempare Template Engine * * * * * * https://github.com/sempare/sempare-delphi-template-engine * @@ -30,7 +30,7 @@ * limitations under the License. * * * *************************************************************************************************%*) - program Sempare.Template.Tester; +program Sempare.Template.Tester; {$IFNDEF TESTINSIGHT} {$APPTYPE CONSOLE} @@ -61,12 +61,13 @@ uses Sempare.Template.TestFunctions in 'tests\Sempare.Template.TestFunctions.pas'; var - runner : ITestRunner; - results : IRunResults; - logger : ITestLogger; - nunitLogger : ITestLogger; + runner: ITestRunner; + results: IRunResults; + logger: ITestLogger; + nunitLogger: ITestLogger; + begin - ReportMemoryLeaksOnShutdown:=true; + ReportMemoryLeaksOnShutdown := true; {$IFDEF TESTINSIGHT} TestInsight.DUnitX.RunRegisteredTests; exit; From e72aee7957de1bf64f7c9329390c753c22195aa9 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:21:39 +0100 Subject: [PATCH 097/138] Update header --- src/Sempare.Template.Functions.pas | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Sempare.Template.Functions.pas b/src/Sempare.Template.Functions.pas index d3e83e8..dc0b814 100644 --- a/src/Sempare.Template.Functions.pas +++ b/src/Sempare.Template.Functions.pas @@ -1,35 +1,35 @@ -(* %************************************************************************************************* - * ___ * - * / __| ___ _ __ _ __ __ _ _ _ ___ * - * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * - * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * - * |_| * - **************************************************************************************************** - * * - * Sempare Template Engine * - * * - * * - * https://github.com/sempare/sempare-delphi-template-engine * - **************************************************************************************************** - * * - * Copyright (c) 2019-2023 Sempare Limited * - * * - * Contact: info@sempare.ltd * - * * - * Licensed under the GPL Version 3.0 or the Sempare Commercial License * - * You may not use this file except in compliance with one of these Licenses. * - * You may obtain a copy of the Licenses at * - * * - * https://www.gnu.org/licenses/gpl-3.0.en.html * - * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the Licenses is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * - *************************************************************************************************% *) +(*%************************************************************************************************** + * ___ * + * / __| ___ _ __ _ __ __ _ _ _ ___ * + * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * + * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * + * |_| * + **************************************************************************************************** + * * + * Sempare Template Engine * + * * + * * + * https://github.com/sempare/sempare-delphi-template-engine * + **************************************************************************************************** + * * + * Copyright (c) 2019-2023 Sempare Limited * + * * + * Contact: info@sempare.ltd * + * * + * Licensed under the GPL Version 3.0 or the Sempare Commercial License * + * You may not use this file except in compliance with one of these Licenses. * + * You may obtain a copy of the Licenses at * + * * + * https://www.gnu.org/licenses/gpl-3.0.en.html * + * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the Licenses is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * + *************************************************************************************************%*) unit Sempare.Template.Functions; interface From d83cbdf9ecd8c02020b49b4b6b55c1801f344de3 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:22:18 +0100 Subject: [PATCH 098/138] Remove eoInternalUseNewLine --- src/Sempare.Template.Context.pas | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index a1eff44..0f253a0 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -77,7 +77,7 @@ interface eoRaiseErrorWhenVariableNotFound, // eoAllowIgnoreNL, // eoStripEmptyLines, // - eoInternalUseNewLine, // + eoShowWhitespace, // eoFlattenTemplate, // eoOptimiseTemplate // ); @@ -370,7 +370,7 @@ procedure TTemplateContext.ApplyTo(const AScope: TStackFrame); constructor TTemplateContext.Create(const AOptions: TTemplateEvaluationOptions); begin - FOptions := AOptions; + FOptions := AOptions + [eoFlattenTemplate, eoOptimiseTemplate]; FMaxRuntimeMs := GDefaultRuntimeMS; FPrettyPrintOutput := GPrettyPrintOutput; SetEncoding(GDefaultEncoding); @@ -586,7 +586,6 @@ procedure TTemplateContext.SetMaxRunTimeMs(const ATimeMS: integer); procedure TTemplateContext.SetNewLine(const ANewLine: string); begin FNewLine := ANewLine; - include(FOptions, eoInternalUseNewLine); end; procedure TTemplateContext.SetOptions(const AOptions: TTemplateEvaluationOptions); From a9cb6b66f42875b8afb2633f5a5e7daf63842a6a Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:23:18 +0100 Subject: [PATCH 099/138] Add const to PrettyPrint() --- src/Sempare.Template.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sempare.Template.pas b/src/Sempare.Template.pas index c15451e..73bdb9d 100644 --- a/src/Sempare.Template.pas +++ b/src/Sempare.Template.pas @@ -95,7 +95,7 @@ Template = class class function Context(const AOptions: TTemplateEvaluationOptions = [eoOptimiseTemplate]): ITemplateContext; inline; static; class function Parser(const AContext: ITemplateContext): ITemplateParser; overload; inline; static; class function Parser(): ITemplateParser; overload; inline; static; - class function PrettyPrint(ATemplate: ITemplate): string; inline; static; + class function PrettyPrint(const ATemplate: ITemplate): string; inline; static; // EVAL output to stream @@ -238,7 +238,7 @@ class function Template.Parser: ITemplateParser; exit(CreateTemplateParser(Context)); end; -class function Template.PrettyPrint(ATemplate: ITemplate): string; +class function Template.PrettyPrint(const ATemplate: ITemplate): string; var LVisitor: ITemplateVisitor; LTemplateVisitor: TPrettyPrintTemplateVisitor; From 5289c620b00ea92b2153ec0275087d7facbff59c Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:24:10 +0100 Subject: [PATCH 100/138] Formatting --- src/Sempare.Template.Util.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sempare.Template.Util.pas b/src/Sempare.Template.Util.pas index 62c01cf..ce17587 100644 --- a/src/Sempare.Template.Util.pas +++ b/src/Sempare.Template.Util.pas @@ -82,7 +82,7 @@ implementation uses SysUtils, WinAPI.Windows, - {$IFDEF SUPPORT_WIN_REGISTRY}Win.{$ENDIF}Registry; +{$IFDEF SUPPORT_WIN_REGISTRY}Win.{$ENDIF}Registry; var GVmwareResolved: boolean; From 91bbbfeba6c11041079f2e8f5c081f95b9b23828 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:28:36 +0100 Subject: [PATCH 101/138] Improve whitespace support --- src/Sempare.Template.AST.pas | 7 +- src/Sempare.Template.Lexer.pas | 84 ++-- src/Sempare.Template.OptimiseVisitor.pas | 76 --- src/Sempare.Template.Parser.pas | 539 +++++++++++----------- src/Sempare.Template.TemplateRegistry.pas | 6 +- 5 files changed, 328 insertions(+), 384 deletions(-) delete mode 100644 src/Sempare.Template.OptimiseVisitor.pas diff --git a/src/Sempare.Template.AST.pas b/src/Sempare.Template.AST.pas index 2e9b58c..cc62b40 100644 --- a/src/Sempare.Template.AST.pas +++ b/src/Sempare.Template.AST.pas @@ -86,6 +86,7 @@ ETemplate = class(Exception); vsInclude, // vsRequire, // vsIgnoreNL, // + vsIgnoreWS, // vsOffset, // vsStep, // vsLimit, // @@ -173,7 +174,6 @@ ETemplate = class(Exception); function GetToken: TTemplateSymbol; function GetStripActions: TStripActionSet; procedure SetToken(const AToken: TTemplateSymbol); - function StripWS: boolean; property Token: TTemplateSymbol read GetToken write SetToken; property Position: IPosition read GetPosition; property StripActions: TStripActionSet read GetStripActions; @@ -211,13 +211,16 @@ ETemplate = class(Exception); property Stmt: IStmt read GetStmt; end; + TParserOption = (poAllowEnd, poAllowElse, poAllowElIf, poHasElse, poInLoop, poStripNL, poStripWS); + TParserOptions = set of TParserOption; + ITemplate = interface(ITemplateVisitorHost) ['{93AAB971-5B4B-4959-93F2-6C7DAE15C91B}'] function GetItem(const AOffset: integer): IStmt; function GetCount: integer; function GetLastItem: IStmt; procedure FlattenTemplate; - procedure OptimiseTemplate(); + procedure OptimiseTemplate(const AOptions: TParserOptions); property Items[const AOffset: integer]: IStmt read GetItem; property Count: integer read GetCount; property LastItem: IStmt read GetLastItem; diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index e588b07..0856313 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -105,8 +105,6 @@ TPair = record FLineOffset: integer; FStartScript: string; FEndScript: string; - FStartStripScript: string; - FEndStripScript: string; FOptions: TTemplateEvaluationOptions; FContext: ITemplateContext; procedure GetInput; @@ -126,14 +124,12 @@ TSimpleTemplateSymbol = class(TInterfacedObject, ITemplateSymbol) private FToken: TTemplateSymbol; FPosition: IPosition; - FStripWS: Boolean; FStripActions: TStripActionSet; function GetPosition: IPosition; public - constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []); + constructor Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripActions: TStripActionSet = []); procedure SetToken(const AToken: TTemplateSymbol); function GetToken: TTemplateSymbol; - function StripWS: Boolean; function GetStripActions: TStripActionSet; end; @@ -169,8 +165,6 @@ constructor TTemplateLexer.Create(const AContext: ITemplateContext; const AStrea FOptions := AContext.Options; FStartScript := AContext.StartToken; FEndScript := AContext.EndToken; - FStartStripScript := AContext.StartStripToken; - FEndStripScript := AContext.EndStripToken; if length(FStartScript) <> 2 then raise ETemplateLexer.CreateRes(@SContextStartTokenMustBeTwoCharsLong); if length(FEndScript) <> 2 then @@ -249,8 +243,6 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; LLine: integer; LPosition: integer; LLast: char; - LEndExpect: char; - LEndStripWS: Boolean; function MakePosition: IPosition; begin @@ -260,12 +252,12 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []; const AGetInput: Boolean = True): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripActions: TStripActionSet = []; const AGetInput: Boolean = True): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripActions); + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripActions); if AGetInput then GetInput; end; @@ -314,25 +306,20 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; function CheckEnd(const ACurrent, ANext: char; const AStripActions: TStripActionSet; const AGetInput: Boolean = false): Boolean; begin - if CharInSet(ACurrent, [FEndScript[1], FEndStripScript[1]]) then + if ACurrent = FEndScript[1] then begin - if ACurrent = FEndScript[1] then - LEndExpect := FEndScript[2] - else - LEndExpect := FEndStripScript[2]; - if ANext = LEndExpect then + if ANext = FEndScript[2] then begin - LEndStripWS := ACurrent = FEndStripScript[1]; if (AStripActions = []) or AGetInput then GetInput; if FAccumulator.length > 0 then begin aResult := ValueToken(vsText); - FNextToken.enqueue(SimpleToken(VsEndScript, LEndStripWS, AStripActions)); + FNextToken.enqueue(SimpleToken(VsEndScript, AStripActions)); end else begin - aResult := SimpleToken(VsEndScript, LEndStripWS, AStripActions); + aResult := SimpleToken(VsEndScript, AStripActions); end; FState := SText; exit(True); @@ -439,21 +426,21 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; if isEndOfScript('+', Result, [saWhitespace, saNL, saKeepOneSpace]) then exit else - exit(SimpleToken(vsPLUS, false, [], false)); + exit(SimpleToken(vsPLUS, [], false)); end; '-': begin if isEndOfScript('-', Result, [saWhitespace]) then exit else - exit(SimpleToken(vsMinus, false, [], false)); + exit(SimpleToken(vsMinus, [], false)); end; '*': begin if isEndOfScript('*', Result, [saWhitespace, saNL]) then exit else - exit(SimpleToken(vsMULT, false, [], false)); + exit(SimpleToken(vsMULT, [], false)); end; '/': exit(SimpleToken(vsSLASH)); @@ -525,7 +512,6 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; var LLine: integer; LPosition: integer; - LIsStartStripWSToken: Boolean; LState: TStripActionSet; function MakePosition: IPosition; @@ -536,12 +522,12 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; exit(TPosition.Create(FFilename, LLine, LPosition)); end; - function SimpleToken(const ASymbol: TTemplateSymbol; const AStripWS: Boolean = false; const AStripActions: TStripActionSet = []): ITemplateSymbol; + function SimpleToken(const ASymbol: TTemplateSymbol; const AStripActions: TStripActionSet = []): ITemplateSymbol; var LPosition: IPosition; begin LPosition := MakePosition; - Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripWS, AStripActions); + Result := TSimpleTemplateSymbol.Create(LPosition, ASymbol, AStripActions); GetInput; end; @@ -556,16 +542,22 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; GetInput; end; - procedure AccumulateChars(const Achars: TCharSet; const ATransform: TFunc); +type + TTransformFunc = reference to function(const Achar: char; out aResult: string): Boolean; + + procedure AccumulateChars(const Achars: TCharSet; const ATransform: TTransformFunc); + var + LChar: string; begin while not FCurrent.Eof and (CharInSet(FCurrent.Input, Achars)) do begin - FAccumulator.Append(FCurrent.Input); + if ATransform(FCurrent.Input, LChar) then + FAccumulator.Append(LChar); GetInput; end; end; - function CanProduceToken(const AToken: TTemplateSymbol; const Achars: TCharSet; out ASymbol: ITemplateSymbol; const ATransform: TFunc; const AFinal: TFunc): Boolean; + function CanProduceToken(const AToken: TTemplateSymbol; const Achars: TCharSet; out ASymbol: ITemplateSymbol; const ATransform: TTransformFunc; const AFinal: TFunc): Boolean; var lstr: string; begin @@ -600,8 +592,7 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; GetInput; while not FCurrent.Eof do begin - LIsStartStripWSToken := (FCurrent.Input = FStartStripScript[1]) and (FLookahead.Input = FStartStripScript[2]); - if (FCurrent.Input = FStartScript[1]) and (FLookahead.Input = FStartScript[2]) or LIsStartStripWSToken then + if (FCurrent.Input = FStartScript[1]) and (FLookahead.Input = FStartScript[2]) then begin Result := ValueToken(vsText); case FLookahead.Input of @@ -619,14 +610,17 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; if LState <> [] then GetInput; FState := SScript; - FNextToken.enqueue(SimpleToken(VsStartScript, LIsStartStripWSToken, LState)); + FNextToken.enqueue(SimpleToken(VsStartScript, LState)); exit(); end; if CanProduceToken(vsNewLine, [#13, #10], Result, - function(c: char): char + function(const c: char; out aResult: string): Boolean begin - exit(c); + if c = #13 then + exit(false); + aResult := FContext.NewLine; + exit(True); end, function(s: string): string begin @@ -641,12 +635,17 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; end; if CanProduceToken(vsWhiteSpace, [' ', #9], Result, - function(c: char): char + function(const c: char; out aResult: string): Boolean begin if (eoConvertTabsToSpaces in FOptions) and (c = #9) then - exit(' ') + aResult := ' ' else - exit(c); + aResult := c; + + if eoShowWhitespace in FOptions then + aResult := #183; + + exit(True); end, function(s: string): string var @@ -706,12 +705,11 @@ procedure TTemplateLexer.SwallowInput; { TSimpleTemplateSymbol } -constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripWS: Boolean; const AStripActions: TStripActionSet); +constructor TSimpleTemplateSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AStripActions: TStripActionSet); begin FStripActions := AStripActions; FToken := AToken; FPosition := APosition; - FStripWS := AStripWS; end; function TSimpleTemplateSymbol.GetPosition: IPosition; @@ -734,16 +732,11 @@ procedure TSimpleTemplateSymbol.SetToken(const AToken: TTemplateSymbol); FToken := AToken; end; -function TSimpleTemplateSymbol.StripWS: Boolean; -begin - exit(FStripWS); -end; - { TTemplateValueSymbol } constructor TTemplateValueSymbol.Create(const APosition: IPosition; const AToken: TTemplateSymbol; const AString: string); begin - inherited Create(APosition, AToken, false); + inherited Create(APosition, AToken); SetValue(AString); end; @@ -795,6 +788,7 @@ initialization AddHashedKeyword('require', vsRequire); AddHashedKeyword('ignorenl', vsIgnoreNL); +AddHashedKeyword('ignorews', vsIgnoreWS); AddHashedKeyword('if', vsIf); AddHashedKeyword('elif', vsElIf); AddHashedKeyword('else', vsElse); diff --git a/src/Sempare.Template.OptimiseVisitor.pas b/src/Sempare.Template.OptimiseVisitor.pas deleted file mode 100644 index c337673..0000000 --- a/src/Sempare.Template.OptimiseVisitor.pas +++ /dev/null @@ -1,76 +0,0 @@ -(*%************************************************************************************************* - * ___ * - * / __| ___ _ __ _ __ __ _ _ _ ___ * - * \__ \ / -_) | ' \ | '_ \ / _` | | '_| / -_) * - * |___/ \___| |_|_|_| | .__/ \__,_| |_| \___| * - * |_| * - **************************************************************************************************** - * * - * Sempare Template Engine * - * * - * * - * https://github.com/sempare/sempare-delphi-template-engine * - **************************************************************************************************** - * * - * Copyright (c) 2019-2023 Sempare Limited * - * * - * Contact: info@sempare.ltd * - * * - * Licensed under the GPL Version 3.0 or the Sempare Commercial License * - * You may not use this file except in compliance with one of these Licenses. * - * You may obtain a copy of the Licenses at * - * * - * https://www.gnu.org/licenses/gpl-3.0.en.html * - * https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/commercial.license.md * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the Licenses is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * - *************************************************************************************************%*) -unit Sempare.Template.OptimiseVisitor; - -interface - -uses - System.Generics.Collections, - Sempare.Template.AST, - Sempare.Template.Common, - Sempare.Template.Visitor; - -type - IOptimiseVisitor = interface(ITemplateVisitor) - ['{98EB0C4D-6AE5-41C4-82AE-FA30DFBE11F5}'] - end; - - TOptimiseVisitor = class(TNoExprTemplateVisitor, IOptimiseVisitor) - private - FFlatten, FOptimise: boolean; - public - constructor Create(const AFlatten, AOptimise: boolean); - - procedure Visit(const ATemplate: ITemplate); overload; override; - end; - -implementation - -{ TOptimiseVisitor } - -constructor TOptimiseVisitor.Create(const AFlatten, AOptimise: boolean); -begin - FFlatten := AFlatten or AOptimise; - FOptimise := AOptimise; -end; - -procedure TOptimiseVisitor.Visit(const ATemplate: ITemplate); -begin - inherited; - if FFlatten then - ATemplate.FlattenTemplate; - if FOptimise then - ATemplate.OptimiseTemplate; -end; - -end. diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 873f3f7..e760a13 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -73,9 +73,8 @@ TTemplate = class(TInterfacedObject, ITemplate, ITemplateAdd, ITemplateVisitor FPosition: IPosition; FArray: TList; - // function Flatten: TArray; procedure FlattenTemplate; - procedure OptimiseTemplate; + procedure OptimiseTemplate(const AOptions: TParserOptions); function GetFilename: string; procedure SetFilename(const AFilename: string); function GetLine: integer; @@ -524,9 +523,6 @@ EEndOfBlock = class(ETemplate); TTemplateSymbolSet = set of TTemplateSymbol; - TParserOption = (poAllowEnd, poAllowElse, poAllowElIf, poHasElse, poInLoop, poStripWS); - TParserOptions = set of TParserOption; - TStripActionStackItem = record BeforeNLStripActions: TStripActionSet; @@ -546,13 +542,14 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) FAfterNLStripActions: TStripActionSet; FStripActionsStack: TStack; - function IsNLStmt(const AStmt: IStmt): boolean; + function IsNLorWSStmt(const AStmt: IStmt; out AIsWS: boolean): boolean; procedure PushStripAction(const AContainer: ITemplate; const ABeforeNLStripActions: TStripActionSet; const AAfterNLStripActions: TStripActionSet); procedure PopStripAction; - function PushContainer: ITemplate; - function PopContainer: ITemplate; + function PushContainer(const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet): ITemplate; + procedure PopContainer; + function CurrentContainer: ITemplate; function lookaheadValue: string; function MatchValues(const ASymbols: TTemplateSymbolSet; out ASymbol: TTemplateSymbol): string; @@ -563,32 +560,41 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) procedure MatchClosingBracket(const AExpect: TTemplateSymbol); function AddStripStmt(const AStmt: IStmt; const AStripActions: TStripActionSet; const ADirection: TStripDirection): IStmt; overload; function AddStripStmt(const ATemplate: ITemplate; const AStripActions: TStripActionSet; const ADirection: TStripDirection): ITemplate; overload; + function WrapWithStripStmt(const AStmt: IStmt; const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet): IStmt; inline; + procedure AddStmt(const ATemplate: ITemplate; const AStmt: IStmt); + function GetValueSeparatorSymbol: TTemplateSymbol; + + private + // helper rules + function RuleIgnoreOption(const ASymbol: TTemplateSymbol; const AOption: TParserOption): IStmt; + function RuleExprList(const AEndToken: TTemplateSymbol = vsCloseRoundBracket): IExprList; + function RuleElIfStmt: IStmt; + function RulePrintStmtVariable(AExpr: IExpr): IStmt; overload; + function RuleAssignStmt(ASymbol: IExpr): IStmt; + function RuleEndStmt: IStmt; private function RuleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; function RuleStmt: IStmt; function RuleIgnoreNewline: IStmt; + function RuleIgnoreWhitespace: IStmt; function RuleCommentStmt: IStmt; function RuleIdStmt: IStmt; function RuleExprStmt: IStmt; function RuleIncludeStmt: IStmt; function RulePrintStmt: IStmt; - function RuleEndStmt: IStmt; function RuleContinueStmt: IStmt; function RuleBreakStmt: IStmt; function RuleIfStmt: IStmt; - function RuleElIfStmt: IStmt; - function RuleExprList(const AEndToken: TTemplateSymbol = vsCloseRoundBracket): IExprList; - function RuleAssignStmt(ASymbol: IExpr): IStmt; - function RulePrintStmtVariable(AExpr: IExpr): IStmt; overload; function RuleForStmt: IStmt; function RuleWhileStmt: IStmt; function RuleWithStmt: IStmt; function RuleCycleStmt: IStmt; function RuleTemplateStmt: IStmt; - function RuleBlockStmt: IStmt; function RuleExtendsStmt: IStmt; + function RuleRequireStmt: IStmt; + function RuleNoopStmt: IStmt; function RuleExpression: IExpr; function RuleSimpleExpression: IExpr; @@ -596,13 +602,9 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function RuleSignedFactor: IExpr; function RuleFactor: IExpr; function RuleVariable: IExpr; - function RuleFunctionExpr(const ASymbol: string): IExpr; function RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; - function RuleRequireStmt: IStmt; - function RuleNoopStmt: IStmt; - function GetValueSeparatorSymbol: TTemplateSymbol; public constructor Create(AContext: ITemplateContext); destructor Destroy; override; @@ -626,17 +628,19 @@ function Flatten(const AStmts: TList): TArray; end; end; -procedure Optimise(const ATemplates: TArray); +procedure Optimise(const AContextOptions: TTemplateEvaluationOptions; const AOptions: TParserOptions; const ATemplates: TArray); var LTemplate: ITemplate; begin for LTemplate in ATemplates do begin - if assigned(LTemplate) then - begin + if not assigned(LTemplate) then + continue; + + if (eoFlattenTemplate in AContextOptions) or (eoOptimiseTemplate in AContextOptions) then LTemplate.FlattenTemplate; - LTemplate.OptimiseTemplate; - end; + if eoOptimiseTemplate in AContextOptions then + LTemplate.OptimiseTemplate(AOptions); end; end; @@ -727,13 +731,14 @@ function TTemplateParser.GetValueSeparatorSymbol: TTemplateSymbol; exit(vsComma); end; -function TTemplateParser.IsNLStmt(const AStmt: IStmt): boolean; +function TTemplateParser.IsNLorWSStmt(const AStmt: IStmt; out AIsWS: boolean): boolean; var LPrintStmt: IPrintStmt; begin if not supports(AStmt, IPrintStmt, LPrintStmt) then exit(false); - exit(supports(LPrintStmt.Expr, INewLineExpr)); + AIsWS := supports(LPrintStmt.Expr, IWhitespaceExpr); + exit(AIsWS or supports(LPrintStmt.Expr, INewLineExpr)); end; procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); @@ -744,6 +749,14 @@ procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); Match(AExpect); end; +procedure TTemplateParser.AddStmt(const ATemplate: ITemplate; const AStmt: IStmt); +var + LAdd: ITemplateAdd; +begin + if supports(ATemplate, ITemplateAdd, LAdd) then + LAdd.Add(AStmt); +end; + function TTemplateParser.AddStripStmt(const ATemplate: ITemplate; const AStripActions: TStripActionSet; const ADirection: TStripDirection): ITemplate; var LAdd: ITemplateAdd; @@ -788,15 +801,11 @@ constructor TTemplateParser.Create(AContext: ITemplateContext); PushStripAction(nil, [], []); end; -const - IF_ELIF_END: TTemplateSymbolSet = [vsELIF, vsElse, vsEND]; - function TTemplateParser.RuleIfStmt: IStmt; var LConditionalExpr: IExpr; LTrueContainer: ITemplate; LFalseContainer: ITemplate; - LContainerAdd: ITemplateAdd; LOptions: IPreserveValue; LSymbol: ITemplateSymbol; LBeforeNLStripActions: TStripActionSet; @@ -811,28 +820,17 @@ function TTemplateParser.RuleIfStmt: IStmt; LBeforeNLStripActions := Match(vsEndScript); // create new container for true condition - PushContainer; - LTrueContainer := self.CurrentContainer; - - PushStripAction(LTrueContainer, LBeforeNLStripActions, LAfterNLStripActions); + LTrueContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); - // AddStripStmt(LTrueContainer, LAfterNLStripActions, sdAfterNewLine); - - RuleStmts(LTrueContainer, IF_ELIF_END); - - PopStripAction(); - - // AddStripStmt(LTrueContainer, LAfterNLStripActions, sdLeft); + RuleStmts(LTrueContainer, [vsELIF, vsElse, vsEND]); PopContainer; - PushContainer; - LFalseContainer := self.CurrentContainer; - LContainerAdd := LFalseContainer as ITemplateAdd; + LFalseContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); if FLookahead.Token = vsELIF then begin while (FLookahead.Token = vsELIF) do begin - LContainerAdd.Add(RuleElIfStmt()); + AddStmt(LFalseContainer, RuleElIfStmt()); end; end else if FLookahead.Token = vsElse then @@ -840,34 +838,36 @@ function TTemplateParser.RuleIfStmt: IStmt; Match(vsElse); Match(vsEndScript); - PushStripAction(LFalseContainer, LBeforeNLStripActions, LAfterNLStripActions); - - - // AddStripStmt(LFalseContainer, LAfterNLStripActions, sdAfterNewLine); - RuleStmts(LFalseContainer, [vsEND]); - - PopStripAction(); - - // AddStripStmt(LFalseContainer, LBeforeNLStripActions, sdBeforeNewLine); - end; PopContainer; Match(vsEND); Match(vsEndScript); - if (eoEvalEarly in FContext.Options) and IsValue(LConditionalExpr) then - begin - if AsBoolean(AsValue(LConditionalExpr)) then - exit(TProcessTemplateStmt.Create(LSymbol.Position, LTrueContainer)) - else if LFalseContainer <> nil then - exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) + try + if LTrueContainer.Count = 0 then + LTrueContainer := nil; + + if LFalseContainer.Count = 0 then + LFalseContainer := nil; + + if not assigned(LTrueContainer) and not assigned(LFalseContainer) then + exit(nil); + + if (eoEvalEarly in FContext.Options) and IsValue(LConditionalExpr) then + begin + if AsBoolean(AsValue(LConditionalExpr)) then + exit(TProcessTemplateStmt.Create(LSymbol.Position, LTrueContainer)) + else if LFalseContainer <> nil then + exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) + end; + result := TIfStmt.Create(LSymbol.Position, LConditionalExpr, LTrueContainer, LFalseContainer); + finally + if result <> nil then + result := WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions); end; - result := TIfStmt.Create(LSymbol.Position, LConditionalExpr, LTrueContainer, LFalseContainer); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; -function TTemplateParser.RuleIgnoreNewline: IStmt; +function TTemplateParser.RuleIgnoreOption(const ASymbol: TTemplateSymbol; const AOption: TParserOption): IStmt; var LSymbol: ITemplateSymbol; LContainerTemplate: ITemplate; @@ -875,27 +875,33 @@ function TTemplateParser.RuleIgnoreNewline: IStmt; LBeforeNLStripActions: TStripActionSet; LAfterNLStripActions: TStripActionSet; begin - LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); + LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd, AOption]); LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; - Match(vsIgnoreNL); + Match(ASymbol); LBeforeNLStripActions := Match(vsEndScript); - PushContainer; - LContainerTemplate := CurrentContainer; - - PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainerTemplate, [vsEND]); - AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); - Match(vsEND); Match(vsEndScript); PopContainer; + result := TProcessTemplateStmt.Create(LSymbol.Position, LContainerTemplate, false); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); +end; + +function TTemplateParser.RuleIgnoreNewline: IStmt; +begin + exit(RuleIgnoreOption(vsIgnoreNL, poStripNL)); +end; + +function TTemplateParser.RuleIgnoreWhitespace: IStmt; +begin + exit(RuleIgnoreOption(vsIgnoreWS, poStripWS)); end; function TTemplateParser.RuleIncludeStmt: IStmt; @@ -909,6 +915,7 @@ function TTemplateParser.RuleIncludeStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsInclude); Match(vsOpenRoundBracket); @@ -921,32 +928,37 @@ function TTemplateParser.RuleIncludeStmt: IStmt; end; MatchClosingBracket(vsCloseRoundBracket); LBeforeNLStripActions := Match(vsEndScript); + + result := TIncludeStmt.Create(LSymbol.Position, LIncludeExpr); + if LScopeExpr <> nil then begin LContainerTemplate := TTemplate.Create(); - LContainerTemplate.Add(TIncludeStmt.Create(LSymbol.Position, LIncludeExpr)); + LContainerTemplate.Add(result); result := TWithStmt.Create(LSymbol.Position, LScopeExpr, LContainerTemplate); - end - else - begin - result := TIncludeStmt.Create(LSymbol.Position, LIncludeExpr); end; - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleRequireStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripActionSet; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; + LExprList: IExprList; begin LSymbol := FLookahead; + + LAfterNLStripActions := FAfterNLStripActions; Match(vsRequire); Match(vsOpenRoundBracket); - result := TRequireStmt.Create(LSymbol.Position, self.RuleExprList()); + LExprList := self.RuleExprList(); MatchClosingBracket(vsCloseRoundBracket); - LStripAction := Match(vsEndScript); - // result := AddStripStmt(result, LStripAction, sdBeforeNewLine); + LBeforeNLStripActions := Match(vsEndScript); + + result := TRequireStmt.Create(LSymbol.Position, LExprList); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; @@ -966,7 +978,8 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl LSymbol: ITemplateSymbol; LLoop: boolean; LEndToken: TTemplateSymbolSet; - LIsNLStmt: boolean; + LIsNLorWS: boolean; + LIsWSStmt: boolean; function AddPrintStmt: IStmt; var @@ -1010,15 +1023,10 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl case LSymbol.Token of vsWhiteSpace, vsNewLine, vsText: begin - if poStripWS in FOptions then - SkipStmt - else - LStmt := AddPrintStmt; + LStmt := AddPrintStmt; end; vsStartScript: begin - if LSymbol.StripWS then - exclude(FOptions, poStripWS); LStmt := RuleStmt; if LStmt = nil then LLoop := false; @@ -1026,18 +1034,20 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl end; if (LStmt <> nil) and not supports(LStmt, IElseStmt) then begin - LIsNLStmt := IsNLStmt(LStmt); - if LIsNLStmt then + LIsNLorWS := IsNLorWSStmt(LStmt, LIsWSStmt); + + if LIsNLorWS and not LIsWSStmt then AddStripStmt(LParentContainer, FStripActionsStack.peek.BeforeNLStripActions, sdBeforeNewLine); - LParentContainer.Add(LStmt); - if LIsNLStmt then + + if not LIsNLorWS or not LIsWSStmt and // + not(poStripNL in FOptions) or // + LIsWSStmt and not(poStripWS in FOptions) then + LParentContainer.Add(LStmt); + + if LIsNLorWS and not LIsWSStmt then AddStripStmt(LParentContainer, FStripActionsStack.peek.AfterNLStripActions, sdAfterNewLine); end; end; - if vsEND in AEndToken then - begin - PopStripAction; - end; end; function TTemplateParser.RuleTemplateStmt: IStmt; @@ -1051,27 +1061,22 @@ function TTemplateParser.RuleTemplateStmt: IStmt; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsTemplate); LExpr := RuleExpression; LBeforeNLStripActions := Match(vsEndScript); - PushContainer; - LContainer := CurrentContainer; - PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(CurrentContainer, [vsEND]); - AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); - Match(vsEND); Match(vsEndScript); PopContainer; + result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - LContainer.FlattenTemplate; - LContainer.OptimiseTemplate; + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleSignedFactor: IExpr; @@ -1175,6 +1180,8 @@ function TTemplateParser.RuleStmt: IStmt; result := RuleContinueStmt; vsIgnoreNL: result := RuleIgnoreNewline; + vsIgnoreWS: + result := RuleIgnoreWhitespace; vsComment: result := RuleCommentStmt; vsInclude: @@ -1208,6 +1215,7 @@ function TTemplateParser.RuleStmt: IStmt; else result := RuleExprStmt; end; + if (eoEmbedException in FContext.Options) and (result <> nil) then begin result := TDebugStmt.Create(result); @@ -1289,38 +1297,37 @@ function TTemplateParser.RuleBlockStmt: IStmt; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsBlock); LName := RuleExpression; LBeforeNLStripActions := Match(vsEndScript); - PushContainer; - LContainer := CurrentContainer; - PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); - AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); - Match(vsEND); Match(vsEndScript); PopContainer; + result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - LContainer.FlattenTemplate; - LContainer.OptimiseTemplate; + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleNoopStmt: IStmt; var LSymbol: ITemplateSymbol; - LStripAction: TStripActionSet; + LBeforeNLStripActions: TStripActionSet; + LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; - LStripAction := Match(vsEndScript); + + LAfterNLStripActions := FAfterNLStripActions; + LBeforeNLStripActions := Match(vsEndScript); + result := TNoopStmt.Create(LSymbol.Position); - // result := AddStripStmt(result, LStripAction, sdBeforeNewLine); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleBreakStmt: IStmt; @@ -1330,14 +1337,16 @@ function TTemplateParser.RuleBreakStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsBreak); LBeforeNLStripActions := Match(vsEndScript); + if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); + result := TBreakStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleCommentStmt: IStmt; @@ -1347,12 +1356,13 @@ function TTemplateParser.RuleCommentStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsComment); LBeforeNLStripActions := Match(vsEndScript); + result := TCommentStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleContinueStmt: IStmt; @@ -1362,14 +1372,16 @@ function TTemplateParser.RuleContinueStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsContinue); LBeforeNLStripActions := Match(vsEndScript); + if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); + result := TContinueStmt.Create(LSymbol.Position); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleCycleStmt: IStmt; @@ -1380,19 +1392,16 @@ function TTemplateParser.RuleCycleStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; - LAfterNLStripActions := FAfterNLStripActions; + LAfterNLStripActions := FAfterNLStripActions; Match(vsCycle); Match(vsOpenRoundBracket); - LListExpr := RuleExprList(); - MatchClosingBracket(vsCloseRoundBracket); LBeforeNLStripActions := Match(vsEndScript); result := TCycleStmt.Create(LSymbol.Position, LListExpr); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleElIfStmt: IStmt; @@ -1417,42 +1426,44 @@ function TTemplateParser.RuleElIfStmt: IStmt; LConditionExpr := RuleExpression; Match(vsEndScript); // create new container for true condition - PushContainer; - LTrueContainer := self.CurrentContainer; - - PushStripAction(LTrueContainer, LBeforeNLStripActions, LAfterNLStripActions); - - RuleStmts(LTrueContainer, IF_ELIF_END); - PopStripAction; + LTrueContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); - AddStripStmt(LTrueContainer, LBeforeNLStripActions, sdBeforeNewLine); + RuleStmts(LTrueContainer, [vsELIF, vsElse, vsEND]); PopContainer; if FLookahead.Token = vsElse then begin Match(vsElse); Match(vsEndScript); - PushContainer; - LFalseContainer := self.CurrentContainer; - - PushStripAction(LFalseContainer, LBeforeNLStripActions, LAfterNLStripActions); + LFalseContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LFalseContainer, [vsEND, vsELIF]); - AddStripStmt(LFalseContainer, LBeforeNLStripActions, sdBeforeNewLine); - PopContainer; end; - if (eoEvalEarly in FContext.Options) and IsValue(LConditionExpr) then - begin - if AsBoolean(AsValue(LConditionExpr)) then - exit(TProcessTemplateStmt.Create(LSymbol.Position, LTrueContainer)) - else if LFalseContainer <> nil then - exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) + + if LTrueContainer.Count = 0 then + LTrueContainer := nil; + + if LFalseContainer.Count = 0 then + LFalseContainer := nil; + + if not assigned(LTrueContainer) and not assigned(LFalseContainer) then + exit(nil); + + try + if (eoEvalEarly in FContext.Options) and IsValue(LConditionExpr) then + begin + if AsBoolean(AsValue(LConditionExpr)) then + exit(TProcessTemplateStmt.Create(LSymbol.Position, LTrueContainer)) + else if LFalseContainer <> nil then + exit(TProcessTemplateStmt.Create(LSymbol.Position, LFalseContainer)) + end; + result := TIfStmt.Create(LSymbol.Position, LConditionExpr, LTrueContainer, LFalseContainer); + finally + if result <> nil then + result := WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions); end; - result := TIfStmt.Create(LSymbol.Position, LConditionExpr, LTrueContainer, LFalseContainer); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); end; function TTemplateParser.RuleEndStmt: IStmt; @@ -1626,6 +1637,7 @@ function TTemplateParser.RuleForStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); + LAfterNLStripActions := FAfterNLStripActions; Match(vsFor); LId := MatchValue(vsID); @@ -1669,8 +1681,8 @@ function TTemplateParser.RuleForStmt: IStmt; end; end; end; - LBeforeNLStripActions := Match(vsEndScript); + LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1680,15 +1692,10 @@ function TTemplateParser.RuleForStmt: IStmt; Match(LBlockSymbol); Match(vsEndScript); end; - PushContainer; - LContainerTemplate := CurrentContainer; - - PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); - AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); - PopContainer; if (i mod 2 = 0) then begin @@ -1703,13 +1710,29 @@ function TTemplateParser.RuleForStmt: IStmt; LOnLoop := LContainerTemplate; end; Match(vsEndScript); + + Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); + + if assigned(LOnLoop) and (LOnLoop.Count = 0) then + LOnLoop := nil; + if assigned(LOnBegin) and (LOnBegin.Count = 0) then + LOnBegin := nil; + if assigned(LOnEnd) and (LOnEnd.Count = 0) then + LOnEnd := nil; + if assigned(LOnEmpty) and (LOnEmpty.Count = 0) then + LOnEmpty := nil; + if assigned(LBetweenItem) and (LBetweenItem.Count = 0) then + LBetweenItem := nil; + + if not assigned(LOnLoop) and not assigned(LOnBegin) and not assigned(LOnEnd) and not assigned(LOnEmpty) and not assigned(LBetweenItem) then + exit(nil); + if LForOp in [TForOp.foIn, TForOp.foOf] then result := TForInStmt.Create(LSymbol.Position, LId, LForOp, LRangeExpr, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem) else result := TForRangeStmt.Create(LSymbol.Position, LId, LForOp, LLowValueExpr, LHighValueExpr, LStep, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); + + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleFunctionExpr(const ASymbol: string): IExpr; @@ -1734,6 +1757,7 @@ function TTemplateParser.RuleIdStmt: IStmt; begin LSymbol := FLookahead; LExpr := RuleVariable; + LAfterNLStripActions := FAfterNLStripActions; if FLookahead.Token = vsCOLONEQ then begin @@ -1747,11 +1771,10 @@ function TTemplateParser.RuleIdStmt: IStmt; else begin RaiseError(LSymbol.Position, format(SParsingErrorExpecting, ['variable reference, function call or assignment'])); - end; LBeforeNLStripActions := Match(vsEndScript); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleWhileStmt: IStmt; @@ -1795,6 +1818,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LSymbol := FLookahead; LOptions := Preserve.Value(FOptions, FOptions + [poInLoop, poAllowEnd]); + LAfterNLStripActions := FAfterNLStripActions; Match(vsWhile); LCondition := RuleExpression; @@ -1814,6 +1838,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; end; end; LBeforeNLStripActions := Match(vsEndScript); + LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; i := 0; @@ -1823,15 +1848,10 @@ function TTemplateParser.RuleWhileStmt: IStmt; Match(LBlockSymbol); Match(vsEndScript); end; - PushContainer; - LContainerTemplate := CurrentContainer; - - PushStripAction(LContainerTemplate, LBeforeNLStripActions, LAfterNLStripActions); + LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); LBlockSymbol := RuleStmts(LContainerTemplate, ONFIRST_ONEND_ONLOOP_ELSE); - AddStripStmt(LContainerTemplate, LBeforeNLStripActions, sdBeforeNewLine); - PopContainer; if (i mod 2 = 0) then begin @@ -1846,13 +1866,28 @@ function TTemplateParser.RuleWhileStmt: IStmt; LOnLoop := LContainerTemplate; end; Match(vsEndScript); + + Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); + + if assigned(LOnLoop) and (LOnLoop.Count = 0) then + LOnLoop := nil; + if assigned(LOnBegin) and (LOnBegin.Count = 0) then + LOnBegin := nil; + if assigned(LOnEnd) and (LOnEnd.Count = 0) then + LOnEnd := nil; + if assigned(LOnEmpty) and (LOnEmpty.Count = 0) then + LOnEmpty := nil; + if assigned(LBetweenItem) and (LBetweenItem.Count = 0) then + LBetweenItem := nil; + + if not assigned(LOnLoop) and not assigned(LOnBegin) and not assigned(LOnEnd) and not assigned(LOnEmpty) and not assigned(LBetweenItem) then + exit(nil); + if (eoEvalEarly in FContext.Options) and IsValue(LCondition) and not AsBoolean(AsValue(LCondition)) then result := nil else result := TWhileStmt.Create(LSymbol.Position, LCondition, LOffsetExpr, LLimitExpr, LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - Optimise(ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleWithStmt: IStmt; @@ -1866,27 +1901,31 @@ function TTemplateParser.RuleWithStmt: IStmt; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowEnd]); LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsWith); LExpr := RuleExpression; LBeforeNLStripActions := Match(vsEndScript); - PushContainer; - LContainer := CurrentContainer; - PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); - AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); - Match(vsEND); Match(vsEndScript); PopContainer; + + if LContainer.Count = 0 then + exit(nil); + result := TWithStmt.Create(LSymbol.Position, LExpr, LContainer); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - LContainer.FlattenTemplate; - LContainer.OptimiseTemplate; + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); +end; + +function TTemplateParser.WrapWithStripStmt(const AStmt: IStmt; const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet): IStmt; +begin + result := AddStripStmt(AStmt, ABeforeNLStripActions, sdRight); + exit(AddStripStmt(result, AAfterNLStripActions, sdLeft)); end; function TTemplateParser.RulePrintStmt: IStmt; @@ -1897,17 +1936,16 @@ function TTemplateParser.RulePrintStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; + LAfterNLStripActions := FAfterNLStripActions; Match(vsPrint); Match(vsOpenRoundBracket); LExpr := RuleExpression; - MatchClosingBracket(vsCloseRoundBracket); LBeforeNLStripActions := Match(vsEndScript); - result := TPrintStmt.Create(LSymbol.Position, LExpr); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + result := TPrintStmt.Create(LSymbol.Position, LExpr); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RulePrintStmtVariable(AExpr: IExpr): IStmt; @@ -1946,14 +1984,13 @@ function TTemplateParser.RuleExprStmt: IStmt; LAfterNLStripActions: TStripActionSet; begin LSymbol := FLookahead; - LAfterNLStripActions := FAfterNLStripActions; + LAfterNLStripActions := FAfterNLStripActions; LExpr := RuleExpression; result := RulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, LExpr)); LBeforeNLStripActions := Match(vsEndScript); - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.RuleExtendsStmt: IStmt; @@ -1971,7 +2008,6 @@ function TTemplateParser.RuleExtendsStmt: IStmt; LSymbol := FLookahead; LAfterNLStripActions := FAfterNLStripActions; - Match(vsExtends); Match(vsOpenRoundBracket); LName := RuleExpression; @@ -1982,32 +2018,24 @@ function TTemplateParser.RuleExtendsStmt: IStmt; end; Match(vsCloseRoundBracket); LBeforeNLStripActions := Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block - PushContainer; - LContainer := CurrentContainer; - - PushStripAction(LContainer, LBeforeNLStripActions, LAfterNLStripActions); + LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); - AddStripStmt(LContainer, LBeforeNLStripActions, sdBeforeNewLine); - Match(vsEND); Match(vsEndScript); PopContainer; + + result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); + if LScopeExpr <> nil then begin LContainerTemplate := TTemplate.Create(); - LContainerTemplate.Add(TExtendsStmt.Create(LSymbol.Position, LName, LContainer)); + LContainerTemplate.Add(result); result := TWithStmt.Create(LSymbol.Position, LScopeExpr, LContainerTemplate); - end - else - begin - result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); end; - result := AddStripStmt(result, LBeforeNLStripActions, sdRight); - result := AddStripStmt(result, LAfterNLStripActions, sdLeft); - LContainer.FlattenTemplate; - LContainer.OptimiseTemplate; + + exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; function TTemplateParser.CurrentContainer: ITemplate; @@ -2041,15 +2069,6 @@ function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripActionSet; result := []; if ASymbol = FLookahead.Token then begin - if LSymbol.StripWS then - begin - case ASymbol of - vsStartScript: - exclude(FOptions, poStripWS); - vsEndScript: - include(FOptions, poStripWS); - end; - end; case ASymbol of vsStartScript: begin @@ -2109,29 +2128,47 @@ function TTemplateParser.MatchValue(const ASymbol: TTemplateSymbol): string; end; function TTemplateParser.Parse(const AStream: TStream; const AManagedStream: boolean): ITemplate; +var + LBeforeNLStripActions, LAfterNLStripActions: TStripActionSet; begin + if eoTrimLines in FContext.Options then + begin + LBeforeNLStripActions := [saWhitespace]; + LAfterNLStripActions := [saWhitespace]; + end + else + begin + LBeforeNLStripActions := []; + LAfterNLStripActions := []; + end; FContainerStack.Clear; - PushContainer; + result := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); FLexer := CreateTemplateLexer(FContext, AStream, '', AManagedStream); FLookahead := FLexer.GetToken; - result := CurrentContainer; if AStream.Size > 0 then begin RuleStmts(result, []); end; Match(vsEOF); - if eoFlattenTemplate in FContext.Options then - result.FlattenTemplate; - if eoOptimiseTemplate in FContext.Options then - result.OptimiseTemplate; + PopContainer; + if eoPrettyPrint in FContext.Options then FContext.PrettyPrintOutput(Template.PrettyPrint(result)); end; -function TTemplateParser.PopContainer: ITemplate; +procedure TTemplateParser.PopContainer; +var + LTemplate: ITemplate; begin - result := CurrentContainer; - FContainerStack.pop; + LTemplate := FContainerStack.pop; + AddStripStmt(LTemplate, FStripActionsStack.peek.AfterNLStripActions, sdLeft); + PopStripAction(); + + if (eoFlattenTemplate in FContext.Options) or (eoOptimiseTemplate in FContext.Options) then + LTemplate.FlattenTemplate; + + if eoOptimiseTemplate in FContext.Options then + LTemplate.OptimiseTemplate(FOptions); end; procedure TTemplateParser.PopStripAction; @@ -2139,10 +2176,12 @@ procedure TTemplateParser.PopStripAction; FStripActionsStack.pop; end; -function TTemplateParser.PushContainer: ITemplate; +function TTemplateParser.PushContainer(const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet): ITemplate; begin - result := CurrentContainer; - FContainerStack.push(TTemplate.Create()); + result := TTemplate.Create(); + PushStripAction(result, ABeforeNLStripActions, AAfterNLStripActions); + FContainerStack.push(result); + AddStripStmt(result, ABeforeNLStripActions, sdRight); end; procedure TTemplateParser.PushStripAction(const AContainer: ITemplate; const ABeforeNLStripActions, AAfterNLStripActions: TStripActionSet); @@ -2170,9 +2209,6 @@ procedure TTemplateParser.PushStripAction(const AContainer: ITemplate; const ABe end; FStripActionsStack.push(TStripActionStackItem.Create(LBeforeNLStripActions, LAfterNLStripActions)); - if assigned(AContainer) then - AddStripStmt(AContainer, LAfterNLStripActions, sdAfterNewLine); - end; { TValueExpr } @@ -2500,11 +2536,6 @@ destructor TTemplate.Destroy; FArray.Free; inherited; end; -{ - function TTemplate.Flatten: TArray; - begin - exit(Sempare.Template.Parser.Flatten(FArray)); - end; } function TTemplate.GetCount: integer; begin @@ -2575,11 +2606,6 @@ procedure TTemplate.FlattenTemplate; var LStmt: IStmt; LStmts: TList; - // i: integer; - // j: integer; - // LScanStmt: IStripStmt; - // LReviewStmt: IStmt; - begin LStmts := TList.Create; try @@ -2598,7 +2624,7 @@ procedure TTemplate.FlattenTemplate; end; end; -procedure TTemplate.OptimiseTemplate; +procedure TTemplate.OptimiseTemplate(const AOptions: TParserOptions); var LStmt: IStmt; LStmts: TList; @@ -2609,9 +2635,17 @@ procedure TTemplate.OptimiseTemplate; function CanStrip(const AAction: TStripActionSet; const AStmt: IStmt): boolean; begin - exit((saNL in AAction) and IsPrintNewlineExpr(AStmt) or // - (saWhitespace in AAction) and IsPrintWhitespaceExpr(AStmt)); - + if IsPrintNewlineExpr(AStmt) then + begin + exit(saNL in AAction); + end; + if IsPrintWhitespaceExpr(AStmt) then + begin + exit(saWhitespace in AAction); + end; + if supports(AStmt, IStripStmt) then + exit(true); + exit(false); end; function KeepSpaceIfRequired(const AAction: TStripActionSet): IStmt; begin @@ -2628,17 +2662,6 @@ procedure TTemplate.OptimiseTemplate; begin LStmts := TList.Create; try - for LStmt in FArray do - begin - if IsAny(LStmt, [IEndStmt, ICommentStmt, IElseStmt, INoopStmt]) then - begin - continue; - end; - LStmts.AddRange(LStmt); - end; - FArray.Clear; - FArray.AddRange(LStmts); - i := 0; while i <= FArray.Count - 1 do begin diff --git a/src/Sempare.Template.TemplateRegistry.pas b/src/Sempare.Template.TemplateRegistry.pas index 565cab8..f052d6f 100644 --- a/src/Sempare.Template.TemplateRegistry.pas +++ b/src/Sempare.Template.TemplateRegistry.pas @@ -71,7 +71,7 @@ TAbstractProxyTemplate = class abstract(TInterfacedObject, ITemplate) function GetCount: integer; function GetLastItem: IStmt; procedure FlattenTemplate; - procedure OptimiseTemplate; + procedure OptimiseTemplate(const AOptions: TParserOptions); procedure Accept(const AVisitor: ITemplateVisitor); function GetFilename: string; procedure SetFilename(const AFilename: string); @@ -581,9 +581,9 @@ procedure TAbstractProxyTemplate.FlattenTemplate; FTemplate.FlattenTemplate; end; -procedure TAbstractProxyTemplate.OptimiseTemplate; +procedure TAbstractProxyTemplate.OptimiseTemplate(const AOptions: TParserOptions); begin - FTemplate.OptimiseTemplate; + FTemplate.OptimiseTemplate(AOptions); end; procedure TAbstractProxyTemplate.SetFilename(const AFilename: string); From 9d1a246a608c3684580127c415c25c57d41b65c9 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 18:30:06 +0100 Subject: [PATCH 102/138] Refactor tests so all include assertions Remove old NL tests --- tests/Sempare.Template.Test.pas | 20 +-- tests/Sempare.Template.TestAssign.pas | 4 +- tests/Sempare.Template.TestContext.pas | 2 +- tests/Sempare.Template.TestDeref.pas | 2 +- tests/Sempare.Template.TestExpr.pas | 18 +-- tests/Sempare.Template.TestFor.pas | 7 +- tests/Sempare.Template.TestFunctions.pas | 4 +- tests/Sempare.Template.TestIf.pas | 2 +- tests/Sempare.Template.TestInclude.pas | 2 +- tests/Sempare.Template.TestLexer.pas | 1 + tests/Sempare.Template.TestNewLineOption.pas | 131 ------------------- 11 files changed, 26 insertions(+), 167 deletions(-) diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 6ebbea7..f801ea6 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -99,9 +99,6 @@ TTestTemplate = class [Test] procedure TestParseFile; - [Test] - procedure TestStripWSScripts; - [Test] // Not Yet Supported procedure TestSemiColon; @@ -324,8 +321,8 @@ TRec = record procedure TTestTemplate.TestNonStmt; begin - Template.Eval('<% %>'); - Template.Eval(' <% %> <% %> '); + Assert.AreEqual('', Template.Eval('<% %>')); + Assert.AreEqual(' ', Template.Eval(' <% %> <% %> ')); end; procedure TTestTemplate.TestNoSpace; @@ -343,6 +340,7 @@ procedure TTestTemplate.TestParseFile; begin // main thing is that we have no exception here! LTemplate := Template.ParseFile('..\..\demo\VelocityDemo\velocity\international.velocity'); + Assert.IsNotNull(LTemplate); end; procedure TTestTemplate.testPrint; @@ -394,16 +392,6 @@ procedure TTestTemplate.TestStmts; end); end; -procedure TTestTemplate.TestStripWSScripts; -begin - Assert.AreEqual('', Template.Eval('<% a := 1 |>2<| a:=3 %>')); - Assert.AreEqual('12345678910', Template.Eval('<% for i := 1 to 10 |><%print(i)%><| end %>')); - Assert.AreEqual('12345678910', Template.Eval('<% for i := 1 to 10 |>'#13#10'<%print(i)%>'#13#10'<| end %>')); - Assert.AreEqual(#$D#$A'1'#$D#$A#$D#$A'2'#$D#$A#$D#$A'3'#$D#$A#$D#$A'4'#$D#$A#$D#$A'5'#$D#$A, Template.Eval('<% for i := 1 to 5 %>'#13#10'<%print(i)%>'#13#10'<% end %>')); - Assert.AreEqual('hellomiddleworld', Template.Eval('<% print("hello") |> this should '#13#10'<% print("middle") %>'#13#10' go missing<| print("world") %>')); - Assert.AreEqual('12345', Template.Eval('<% for i:=1 to 5 |> <%i%> '#13#10'<| end %>')); -end; - { TTestClass } constructor TTestClass.Create(const AData: string); begin @@ -510,7 +498,7 @@ class procedure TMyExceptProc.RaiseExcept(const AValue: string); procedure TTestTemplate.TestEmpty; begin - Template.Eval(''); + Assert.AreEqual('', Template.Eval('')); end; procedure TTestTemplate.TestException; diff --git a/tests/Sempare.Template.TestAssign.pas b/tests/Sempare.Template.TestAssign.pas index 6d5c92a..ad41d2b 100644 --- a/tests/Sempare.Template.TestAssign.pas +++ b/tests/Sempare.Template.TestAssign.pas @@ -56,7 +56,7 @@ implementation procedure TTestTemplateAssign.TestSimpleAssignment; begin - Template.parse('before <% abc := true %> after <% abc %> final'); + Assert.IsNotNull(Template.parse('before <% abc := true %> after <% abc %> final')); end; type @@ -80,7 +80,7 @@ procedure TTestTemplateAssign.TestAssignFunctionCall; begin ctx := Template.Context; ctx.Functions.addfunctions(TDoSomething); - Template.parse(ctx, 'before <% abc := sum(3,4,5) %> after <% abc %> final'); + Assert.IsNotNull(Template.parse(ctx, 'before <% abc := sum(3,4,5) %> after <% abc %> final')); end; initialization diff --git a/tests/Sempare.Template.TestContext.pas b/tests/Sempare.Template.TestContext.pas index 138342e..873550a 100644 --- a/tests/Sempare.Template.TestContext.pas +++ b/tests/Sempare.Template.TestContext.pas @@ -71,7 +71,7 @@ procedure TContextTest.TestCRNLTAB; ctx: ITemplateContext; begin ctx := Template.Context(); - Assert.AreEqual(ctx.NewLine, Template.Eval('<% nl %>')); + Assert.AreEqual(#10, Template.Eval('<% nl %>')); Assert.AreEqual(#13, Template.Eval('<% cr %>')); Assert.AreEqual(#13#10, Template.Eval('<% crnl %>')); Assert.AreEqual(#9, Template.Eval('<% tab %>')); diff --git a/tests/Sempare.Template.TestDeref.pas b/tests/Sempare.Template.TestDeref.pas index 8396d0d..f2da21b 100644 --- a/tests/Sempare.Template.TestDeref.pas +++ b/tests/Sempare.Template.TestDeref.pas @@ -131,7 +131,7 @@ procedure TTestTemplateDeref.TestExprDrefef; procedure TTestTemplateDeref.TestSimpleDrefef; begin - Template.parse('before <% a.b %> after '); + Assert.IsNotNull(Template.parse('before <% a.b %> after ')); end; procedure TTestTemplateDeref.TestWith; diff --git a/tests/Sempare.Template.TestExpr.pas b/tests/Sempare.Template.TestExpr.pas index 97714ba..cdac28a 100644 --- a/tests/Sempare.Template.TestExpr.pas +++ b/tests/Sempare.Template.TestExpr.pas @@ -70,7 +70,7 @@ implementation procedure TTestTemplateExpr.TestSimpleVariable; begin - Template.parse('before <% abc %> after'); + Assert.IsNotNull(Template.parse('before <% abc %> after')); end; procedure TTestTemplateExpr.TestTernary; @@ -81,7 +81,7 @@ procedure TTestTemplateExpr.TestTernary; procedure TTestTemplateExpr.TestExprNum; begin - Template.parse('before <% a := 123 %> after '); + Assert.IsNotNull(Template.parse('before <% a := 123 %> after ')); end; procedure TTestTemplateExpr.TestExprNumDiv; @@ -93,12 +93,12 @@ procedure TTestTemplateExpr.TestExprNumDiv; procedure TTestTemplateExpr.TestExprNumFloat; begin - Template.parse('before <% a := 123.45 %> after '); + Assert.IsNotNull(Template.parse('before <% a := 123.45 %> after ')); end; procedure TTestTemplateExpr.TestExprStr; begin - Template.parse('before <% a := ''hello world'' %> after '); + Assert.IsNotNull(Template.parse('before <% a := ''hello world'' %> after ')); end; procedure TTestTemplateExpr.TestInExpr; @@ -134,11 +134,11 @@ procedure TTestTemplateExpr.TestNotEqual; procedure TTestTemplateExpr.TestExprBool; begin - Template.parse('before <% a := true %> after '); - Template.parse('before <% a:= false %> after '); - Template.parse('before <% a:= true and false %> after '); - Template.parse('before <% a:= true or false %> after '); - Template.parse('before <% a:= true and false or true %> after '); + Assert.IsNotNull(Template.parse('before <% a := true %> after ')); + Assert.IsNotNull(Template.parse('before <% a:= false %> after ')); + Assert.IsNotNull(Template.parse('before <% a:= true and false %> after ')); + Assert.IsNotNull(Template.parse('before <% a:= true or false %> after ')); + Assert.IsNotNull(Template.parse('before <% a:= true and false or true %> after ')); end; initialization diff --git a/tests/Sempare.Template.TestFor.pas b/tests/Sempare.Template.TestFor.pas index 0f0a951..aabe2c6 100644 --- a/tests/Sempare.Template.TestFor.pas +++ b/tests/Sempare.Template.TestFor.pas @@ -426,17 +426,18 @@ procedure TTestTemplateFor.TestWithInlineArray; procedure TTestTemplateFor.TestSimpleFor; begin - Template.parse('before <% for a := 1 to 10 %> after <% end %> '); + Assert.AreEqual('before after', Template.Eval('before <% for a := 1 to 10 %> <% end %> after')); end; procedure TTestTemplateFor.TestSimpleForDownTo; begin - Template.parse('before <% for a := 1 downto 10 %> after <% end %> '); + Assert.AreEqual('before after', Template.Eval('before <% for a := 1 downto 10 %> <% end %> after')); + Assert.AreEqual('before after', Template.Eval('before <% for a := 10 downto 1 %> <% end %> after')); end; procedure TTestTemplateFor.TestSimpleForin; begin - Template.parse('before <% for a in b %> pre <% a %> post <% end %> '); + Assert.IsNotNull(Template.parse('before <% for a in b %> pre <% a %> post <% end %> ')); end; type diff --git a/tests/Sempare.Template.TestFunctions.pas b/tests/Sempare.Template.TestFunctions.pas index 4199fbf..f2fac62 100644 --- a/tests/Sempare.Template.TestFunctions.pas +++ b/tests/Sempare.Template.TestFunctions.pas @@ -295,7 +295,7 @@ procedure TFunctionTest.TestChrOrd; LCtx: ITemplateContext; begin LCtx := Template.Context(); - Assert.AreEqual(LCtx.NewLine, Template.Eval('<% chr(10) %>')); + Assert.AreEqual(#10, Template.Eval('<% chr(10) %>')); Assert.AreEqual(#9, Template.Eval('<% chr(9) %>')); Assert.AreEqual('200', Template.Eval('<% ord(chr(200)) %>')); end; @@ -417,7 +417,7 @@ procedure TFunctionTest.TestPadding; Assert.AreEqual('123000', Template.Eval('<% padright(123, 6, "0") %>')); Assert.AreEqual(#9#9#9#9#9, Template.Eval('<% tabs(5) %>')); Assert.AreEqual(' ', Template.Eval('<% spaces(5) %>')); - Assert.AreEqual(LCtx.NewLine + LCtx.NewLine + LCtx.NewLine + LCtx.NewLine + LCtx.NewLine, Template.Eval('<% nl(5) %>')); + Assert.AreEqual(#10#10#10#10#10, Template.Eval('<% nl(5) %>')); Assert.AreEqual(#13#10#13#10, Template.Eval('<% crnl(2) %>')); end; diff --git a/tests/Sempare.Template.TestIf.pas b/tests/Sempare.Template.TestIf.pas index d5d0165..2767320 100644 --- a/tests/Sempare.Template.TestIf.pas +++ b/tests/Sempare.Template.TestIf.pas @@ -223,7 +223,7 @@ procedure TTestTemplateIf.TestIfWithContinue; procedure TTestTemplateIf.TestNestedIf; begin - Template.parse('before <% if (true) %> pre <% if (true) %> midd;e <% end %> post <% end %> '); + Assert.IsNotNull(Template.parse('before <% if (true) %> pre <% if (true) %> midd;e <% end %> post <% end %> ')); end; type diff --git a/tests/Sempare.Template.TestInclude.pas b/tests/Sempare.Template.TestInclude.pas index a54ccbd..a9cdc2a 100644 --- a/tests/Sempare.Template.TestInclude.pas +++ b/tests/Sempare.Template.TestInclude.pas @@ -692,7 +692,7 @@ TTemplateData = record LStopWatch.Stop; LElapsedMs := LStopWatch.ElapsedMilliseconds / LIterations; {$IF defined( WIN32) OR defined(WIN64)} - Assert.IsTrue(LElapsedMs <= GetTestTimeTollerance(0.15, 6.0)); + Assert.IsTrue(LElapsedMs <= GetTestTimeTollerance(0.25, 6.0)); {$ENDIF} end; diff --git a/tests/Sempare.Template.TestLexer.pas b/tests/Sempare.Template.TestLexer.pas index 2e07ce7..98a2e82 100644 --- a/tests/Sempare.Template.TestLexer.pas +++ b/tests/Sempare.Template.TestLexer.pas @@ -154,6 +154,7 @@ procedure TTestTemplateLexer.TestLexer; symbol := Lexer.GetToken; if symbol.Token = VsEOF then break; + Assert.AreNotEqual(VsEOF, symbol.Token); val := ''; if supports(symbol, ITemplateValueSymbol, vs) then begin diff --git a/tests/Sempare.Template.TestNewLineOption.pas b/tests/Sempare.Template.TestNewLineOption.pas index d4daca7..4cee8f8 100644 --- a/tests/Sempare.Template.TestNewLineOption.pas +++ b/tests/Sempare.Template.TestNewLineOption.pas @@ -42,12 +42,6 @@ interface [TestFixture] TTestNewLineOption = class public - [Test] - procedure TestRecurringSpaces; - [Test] - procedure TestRecurringNLAndSpaces; - [Test] - procedure TestRecurringOnlyNL; [Test] procedure TestNewLine; [Test] @@ -56,10 +50,6 @@ TTestNewLineOption = class procedure TestNewLineWithContext; [Test] procedure TestNL; - [Test] - procedure RemoveEmptyAndStripLines; - [Test] - procedure RemoveEmptyLines; end; implementation @@ -122,127 +112,6 @@ procedure TTestNewLineOption.TestNL; Assert.AreEqual('hello'#13#10#13#10, s); end; -procedure TTestNewLineOption.TestRecurringNLAndSpaces; -// var -// s: TStringStream; -// w: TNewLineStreamWriter; -// str:string; -begin - // s := TStringStream.Create; - // w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines, eoStripRecurringNewlines]); - // try - // w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - // finally - // w.Free; - // str := s.datastring; - // Assert.AreEqual(#10'hello'#10'world'#10, str); - // s.Free; - // end; -end; - -procedure TTestNewLineOption.TestRecurringOnlyNL; -// var -// s: TStringStream; -// w: TNewLineStreamWriter; -// str: string; -begin - // s := TStringStream.Create; - // w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripRecurringNewlines]); - // try - // w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - // finally - // w.Free; - // str := s.datastring; - // Assert.AreEqual(#10' hello '#10' world '#10, str); - // s.Free; - // end; -end; - -procedure TTestNewLineOption.TestRecurringSpaces; -{ var - s: TStringStream; - w: TNewLineStreamWriter; - s2: string; } -begin - { s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); - try - w.Write(' '#10#10' '#10#10); - finally - w.Free; - s2 := s.datastring; - Assert.AreEqual(' '#10#10' '#10#10, s2); - s.Free; - end; - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); - try - w.Write(' '#10#10' '#10#10); - finally - w.Free; - s2 := s.datastring; - Assert.AreEqual(''#10#10''#10#10, s2); - s.Free; - end; - - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, []); - try - w.Write(' hello '#10#10' world '); - finally - w.Free; - s2 := s.datastring; - Assert.AreEqual(' hello '#10#10' world ', s2); - s.Free; - end; - s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoTrimLines]); - try - w.Write(' hello '#10#10' world '); - finally - w.Free; - s2 := s.datastring; - Assert.AreEqual('hello'#10#10'world', s2); - s.Free; - end; } -end; - -procedure TTestNewLineOption.RemoveEmptyAndStripLines; -{ var - s: TStringStream; - w: TNewLineStreamWriter; - str: string; } -begin - { s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines, eoTrimLines]); - try - w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally - w.Free; - str := s.datastring; - Assert.AreEqual('hello'#10'world'#10, str); - s.Free; - end; } -end; - -procedure TTestNewLineOption.RemoveEmptyLines; -{ var - s: TStringStream; - w: TNewLineStreamWriter; - str: string; } -begin - { s := TStringStream.Create; - w := TNewLineStreamWriter.Create(s, TEncoding.ASCII, #10, [eoStripEmptyLines]); - try - w.Write(#10#10#10#10#10' hello '#10#10#10#10' world '#10#10#10#10); - finally - w.Free; - str := s.datastring; - Assert.AreEqual(' hello '#10' world '#10, str); - s.Free; - end; } -end; - procedure TTestNewLineOption.TestNewLine; type TRec = record From 9bf94681063787a4b86d41419954e86e335583ed Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 21:37:35 +0100 Subject: [PATCH 103/138] Added statement separator support with semicolons --- src/Sempare.Template.Parser.pas | 136 +++++++++++++++++++++----------- tests/Sempare.Template.Test.pas | 12 +-- 2 files changed, 94 insertions(+), 54 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index e760a13..394c060 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -533,6 +533,7 @@ TStripActionStackItem = record TTemplateParser = class(TInterfacedObject, ITemplateParser) private FContext: ITemplateContext; + FSemiColon: ITemplateSymbol; FLookahead: ITemplateSymbol; FLexer: ITemplateLexer; FContainerStack: TStack; @@ -555,6 +556,8 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function MatchValues(const ASymbols: TTemplateSymbolSet; out ASymbol: TTemplateSymbol): string; function MatchValue(const ASymbol: TTemplateSymbol): string; procedure Match(ASymbol: ITemplateSymbol); overload; inline; + function Match(ASymbols: TTemplateSymbolSet; var AMatchSymbol: TTemplateSymbol): TStripActionSet; overload; inline; + function MatchEndOfScript: TStripActionSet; function Match(const ASymbol: TTemplateSymbol): TStripActionSet; overload; function MatchNumber(const ASymbol: TTemplateSymbol): extended; procedure MatchClosingBracket(const AExpect: TTemplateSymbol); @@ -749,6 +752,14 @@ procedure TTemplateParser.MatchClosingBracket(const AExpect: TTemplateSymbol); Match(AExpect); end; +function TTemplateParser.MatchEndOfScript: TStripActionSet; +var + LSym: TTemplateSymbol; +begin + LSym := vsEndScript; + exit(Match([vsEndScript, vsSemiColon], LSym)); +end; + procedure TTemplateParser.AddStmt(const ATemplate: ITemplate; const AStmt: IStmt); var LAdd: ITemplateAdd; @@ -817,7 +828,7 @@ function TTemplateParser.RuleIfStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsIF); LConditionalExpr := RuleExpression; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; // create new container for true condition LTrueContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); @@ -836,13 +847,13 @@ function TTemplateParser.RuleIfStmt: IStmt; else if FLookahead.Token = vsElse then begin Match(vsElse); - Match(vsEndScript); + MatchEndOfScript; RuleStmts(LFalseContainer, [vsEND]); end; PopContainer; Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; try if LTrueContainer.Count = 0 then LTrueContainer := nil; @@ -880,14 +891,15 @@ function TTemplateParser.RuleIgnoreOption(const ASymbol: TTemplateSymbol; const LAfterNLStripActions := FAfterNLStripActions; Match(ASymbol); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainerTemplate, [vsEND]); Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; + PopContainer; result := TProcessTemplateStmt.Create(LSymbol.Position, LContainerTemplate, false); @@ -927,7 +939,7 @@ function TTemplateParser.RuleIncludeStmt: IStmt; LScopeExpr := RuleExpression; end; MatchClosingBracket(vsCloseRoundBracket); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TIncludeStmt.Create(LSymbol.Position, LIncludeExpr); @@ -955,7 +967,7 @@ function TTemplateParser.RuleRequireStmt: IStmt; Match(vsOpenRoundBracket); LExprList := self.RuleExprList(); MatchClosingBracket(vsCloseRoundBracket); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TRequireStmt.Create(LSymbol.Position, LExprList); exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); @@ -1019,18 +1031,27 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl begin exit(LSymbol.Token); end; - LStmt := nil; - case LSymbol.Token of - vsWhiteSpace, vsNewLine, vsText: - begin - LStmt := AddPrintStmt; - end; - vsStartScript: - begin - LStmt := RuleStmt; - if LStmt = nil then - LLoop := false; - end; + if assigned(FSemiColon) then + begin + LStmt := RuleStmt; + if LStmt = nil then + LLoop := false; + end + else + begin + LStmt := nil; + case LSymbol.Token of + vsWhiteSpace, vsNewLine, vsText: + begin + LStmt := AddPrintStmt; + end; + vsSemiColon, vsStartScript: + begin + LStmt := RuleStmt; + if LStmt = nil then + LLoop := false; + end; + end; end; if (LStmt <> nil) and not supports(LStmt, IElseStmt) then begin @@ -1065,14 +1086,14 @@ function TTemplateParser.RuleTemplateStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsTemplate); LExpr := RuleExpression; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(CurrentContainer, [vsEND]); Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; PopContainer; result := TDefineTemplateStmt.Create(LSymbol.Position, LExpr, LContainer); @@ -1167,10 +1188,19 @@ function TTemplateParser.RuleTerm: IExpr; function TTemplateParser.RuleStmt: IStmt; var LSymbol: ITemplateSymbol; + LToken: TTemplateSymbol; begin result := nil; LSymbol := FLookahead; - FAfterNLStripActions := Match(vsStartScript); + LToken := vsStartScript; + if assigned(FSemiColon) then + begin + FAfterNLStripActions := FStripActionsStack.peek.AfterNLStripActions; + end + else + begin + FAfterNLStripActions := Match(vsStartScript); + end; case FLookahead.Token of vsEndScript: result := RuleNoopStmt; @@ -1301,14 +1331,14 @@ function TTemplateParser.RuleBlockStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsBlock); LName := RuleExpression; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; PopContainer; result := TBlockStmt.Create(LSymbol.Position, LName, LContainer); @@ -1324,7 +1354,7 @@ function TTemplateParser.RuleNoopStmt: IStmt; LSymbol := FLookahead; LAfterNLStripActions := FAfterNLStripActions; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TNoopStmt.Create(LSymbol.Position); exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); @@ -1340,7 +1370,7 @@ function TTemplateParser.RuleBreakStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsBreak); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); @@ -1359,7 +1389,7 @@ function TTemplateParser.RuleCommentStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsComment); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TCommentStmt.Create(LSymbol.Position); exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); @@ -1375,7 +1405,7 @@ function TTemplateParser.RuleContinueStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsContinue); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; if not(poInLoop in FOptions) then RaiseError(LSymbol.Position, SContinueShouldBeInALoop); @@ -1398,7 +1428,7 @@ function TTemplateParser.RuleCycleStmt: IStmt; Match(vsOpenRoundBracket); LListExpr := RuleExprList(); MatchClosingBracket(vsCloseRoundBracket); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TCycleStmt.Create(LSymbol.Position, LListExpr); exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); @@ -1424,7 +1454,7 @@ function TTemplateParser.RuleElIfStmt: IStmt; LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poHasElse, poAllowEnd]); Match(vsELIF); LConditionExpr := RuleExpression; - Match(vsEndScript); + MatchEndOfScript; // create new container for true condition LTrueContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); @@ -1434,7 +1464,7 @@ function TTemplateParser.RuleElIfStmt: IStmt; if FLookahead.Token = vsElse then begin Match(vsElse); - Match(vsEndScript); + MatchEndOfScript; LFalseContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LFalseContainer, [vsEND, vsELIF]); @@ -1681,7 +1711,7 @@ function TTemplateParser.RuleForStmt: IStmt; end; end; end; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; @@ -1690,7 +1720,7 @@ function TTemplateParser.RuleForStmt: IStmt; if (i > 1) and (i mod 2 = 0) then begin Match(LBlockSymbol); - Match(vsEndScript); + MatchEndOfScript; end; LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); @@ -1709,7 +1739,7 @@ function TTemplateParser.RuleForStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - Match(vsEndScript); + MatchEndOfScript; Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); @@ -1772,7 +1802,7 @@ function TTemplateParser.RuleIdStmt: IStmt; begin RaiseError(LSymbol.Position, format(SParsingErrorExpecting, ['variable reference, function call or assignment'])); end; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; @@ -1837,7 +1867,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; end; end; end; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LBlockSymbol := vsInvalid; LPrevSymbol := vsInvalid; @@ -1846,7 +1876,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; if (i > 1) and (i mod 2 = 0) then begin Match(LBlockSymbol); - Match(vsEndScript); + MatchEndOfScript; end; LContainerTemplate := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); @@ -1865,7 +1895,7 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - Match(vsEndScript); + MatchEndOfScript; Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); @@ -1905,14 +1935,14 @@ function TTemplateParser.RuleWithStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; Match(vsWith); LExpr := RuleExpression; - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; PopContainer; if LContainer.Count = 0 then @@ -1942,7 +1972,7 @@ function TTemplateParser.RulePrintStmt: IStmt; Match(vsOpenRoundBracket); LExpr := RuleExpression; MatchClosingBracket(vsCloseRoundBracket); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; result := TPrintStmt.Create(LSymbol.Position, LExpr); exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); @@ -1988,7 +2018,7 @@ function TTemplateParser.RuleExprStmt: IStmt; LAfterNLStripActions := FAfterNLStripActions; LExpr := RuleExpression; result := RulePrintStmtVariable(TEncodeExpr.Create(LSymbol.Position, LExpr)); - LBeforeNLStripActions := Match(vsEndScript); + LBeforeNLStripActions := MatchEndOfScript; exit(WrapWithStripStmt(result, LBeforeNLStripActions, LAfterNLStripActions)); end; @@ -2017,13 +2047,13 @@ function TTemplateParser.RuleExtendsStmt: IStmt; LScopeExpr := RuleExpression; end; Match(vsCloseRoundBracket); - LBeforeNLStripActions := Match(vsEndScript); // we don't care about the strip action on this as content is ignored inside an extends block + LBeforeNLStripActions := MatchEndOfScript; // we don't care about the strip action on this as content is ignored inside an extends block LContainer := PushContainer(LBeforeNLStripActions, LAfterNLStripActions); RuleStmts(LContainer, [vsEND]); Match(vsEND); - Match(vsEndScript); + MatchEndOfScript; PopContainer; result := TExtendsStmt.Create(LSymbol.Position, LName, LContainer); @@ -2087,6 +2117,24 @@ function TTemplateParser.Match(const ASymbol: TTemplateSymbol): TStripActionSet; RaiseError(LSymbol.Position, format(SParsingErrorExpecting, [TemplateSymbolToString(ASymbol)])); end; +function TTemplateParser.Match(ASymbols: TTemplateSymbolSet; var AMatchSymbol: TTemplateSymbol): TStripActionSet; +var + LSymbol: ITemplateSymbol; +begin + LSymbol := FLookahead; + if FLookahead.Token in ASymbols then + begin + AMatchSymbol := FLookahead.Token; + if AMatchSymbol = vsSemiColon then + begin + FSemiColon := FLookahead; + end; + FLookahead := FLexer.GetToken; + exit(LSymbol.StripActions); + end; + RaiseError(LSymbol.Position, format(SParsingErrorExpecting, [TemplateSymbolToString(AMatchSymbol)])); +end; + procedure TTemplateParser.Match(ASymbol: ITemplateSymbol); begin Match(ASymbol.Token); diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index f801ea6..237f653 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -366,11 +366,7 @@ TInfo = record procedure TTestTemplate.TestSemiColon; begin - Assert.WillRaise( - procedure - begin - Assert.AreEqual('hello world', Template.Eval('<% a:= "hello" ; b:= "world" ; print(a + " " + b) %>')); - end); + Assert.AreEqual('hello world', Template.Eval('<% a:= "hello" ; b:= "world" ; print(a + " " + b) %>')); end; procedure TTestTemplate.TestStartEndToken; @@ -385,11 +381,7 @@ procedure TTestTemplate.TestStartEndToken; procedure TTestTemplate.TestStmts; begin - Assert.WillRaise( - procedure - begin // statement seperator is not supported. Need to review statment parsing to support this - Assert.AreEqual('1', Template.Eval('<% a := 1; print(a) %>')); - end); + Assert.AreEqual('1', Template.Eval('<% a := 1; print(a) %>')); end; { TTestClass } From 9d3383488649c3b520aeb90d21f649a1118ee5e3 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 21:38:53 +0100 Subject: [PATCH 104/138] Remove unused variable --- src/Sempare.Template.Parser.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 394c060..65fe07b 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -1188,11 +1188,9 @@ function TTemplateParser.RuleTerm: IExpr; function TTemplateParser.RuleStmt: IStmt; var LSymbol: ITemplateSymbol; - LToken: TTemplateSymbol; begin result := nil; LSymbol := FLookahead; - LToken := vsStartScript; if assigned(FSemiColon) then begin FAfterNLStripActions := FStripActionsStack.peek.AfterNLStripActions; From 5c910ce44b9f9afe91b2ffefae0afeca8f27643e Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 23:04:05 +0100 Subject: [PATCH 105/138] Tweaks for semicolon stmt support --- src/Sempare.Template.Parser.pas | 36 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 65fe07b..20af3d2 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -757,7 +757,11 @@ function TTemplateParser.MatchEndOfScript: TStripActionSet; LSym: TTemplateSymbol; begin LSym := vsEndScript; - exit(Match([vsEndScript, vsSemiColon], LSym)); + result := Match([vsEndScript, vsSemiColon], LSym); + if LSym = vsEndScript then + begin + FSemiColon := nil; + end; end; procedure TTemplateParser.AddStmt(const ATemplate: ITemplate; const AStmt: IStmt); @@ -821,6 +825,7 @@ function TTemplateParser.RuleIfStmt: IStmt; LSymbol: ITemplateSymbol; LBeforeNLStripActions: TStripActionSet; LAfterNLStripActions: TStripActionSet; + LEndStripActions: TStripActionSet; begin LOptions := Preserve.Value(FOptions, FOptions + [poAllowElse, poAllowEnd, poAllowElIf]); LSymbol := FLookahead; @@ -853,7 +858,11 @@ function TTemplateParser.RuleIfStmt: IStmt; end; PopContainer; Match(vsEND); - MatchEndOfScript; + LEndStripActions := MatchEndOfScript; + + if LBeforeNLStripActions = [] then + LBeforeNLStripActions := LEndStripActions; + try if LTrueContainer.Count = 0 then LTrueContainer := nil; @@ -986,7 +995,7 @@ function TTemplateParser.RuleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; var LStmt: IStmt; - LParentContainer: ITemplateAdd; + LParentContainer: ITemplate; LSymbol: ITemplateSymbol; LLoop: boolean; LEndToken: TTemplateSymbolSet; @@ -1022,7 +1031,7 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl begin result := vsInvalid; LEndToken := AEndToken + [vsEOF]; - LParentContainer := Container as ITemplateAdd; + LParentContainer := Container; LLoop := true; while LLoop do begin @@ -1063,10 +1072,13 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl if not LIsNLorWS or not LIsWSStmt and // not(poStripNL in FOptions) or // LIsWSStmt and not(poStripWS in FOptions) then - LParentContainer.Add(LStmt); + begin + AddStmt(LParentContainer, LStmt); + end; if LIsNLorWS and not LIsWSStmt then AddStripStmt(LParentContainer, FStripActionsStack.peek.AfterNLStripActions, sdAfterNewLine); + end; end; end; @@ -1635,7 +1647,7 @@ function TTemplateParser.RuleForStmt: IStmt; i: integer; LBeforeNLStripActions: TStripActionSet; LAfterNLStripActions: TStripActionSet; - + LEndStripActions: TStripActionSet; procedure ResolveTemplate(const ASymbol: TTemplateSymbol); begin case ASymbol of @@ -1737,7 +1749,10 @@ function TTemplateParser.RuleForStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - MatchEndOfScript; + LEndStripActions := MatchEndOfScript; + + if LBeforeNLStripActions = [] then + LBeforeNLStripActions := LEndStripActions; Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); @@ -1817,6 +1832,8 @@ function TTemplateParser.RuleWhileStmt: IStmt; i: integer; LBeforeNLStripActions: TStripActionSet; LAfterNLStripActions: TStripActionSet; + LEndStripActions: TStripActionSet; + procedure ResolveTemplate(const ASymbol: TTemplateSymbol); begin case ASymbol of @@ -1893,7 +1910,10 @@ function TTemplateParser.RuleWhileStmt: IStmt; begin LOnLoop := LContainerTemplate; end; - MatchEndOfScript; + LEndStripActions := MatchEndOfScript; + + if LBeforeNLStripActions = [] then + LBeforeNLStripActions := LEndStripActions; Optimise(FContext.Options, FOptions, ArrayOfTemplate([LOnLoop, LOnBegin, LOnEnd, LOnEmpty, LBetweenItem])); From db938dc05a9d147ca8febbd8aacd87e85c82543d Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 23:31:47 +0100 Subject: [PATCH 106/138] Support for hash comment --- src/Sempare.Template.Lexer.pas | 19 ++++++++++--------- src/Sempare.Template.Parser.pas | 4 ++++ tests/Sempare.Template.Test.pas | 11 ----------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Sempare.Template.Lexer.pas b/src/Sempare.Template.Lexer.pas index 0856313..1f6f6d6 100644 --- a/src/Sempare.Template.Lexer.pas +++ b/src/Sempare.Template.Lexer.pas @@ -402,15 +402,7 @@ function TTemplateLexer.GetScriptToken: ITemplateSymbol; ',': exit(SimpleToken(vsComma)); '(': - begin - if not Expecting('*') then - exit(SimpleToken(vsOpenRoundBracket)); - SwallowInput; - while not FLookahead.Eof and not((FCurrent.Input = '*') and Expecting(')')) do - SwallowInput; - SwallowInput; - exit(SimpleToken(vsComment)); - end; + exit(SimpleToken(vsOpenRoundBracket)); ')': exit(SimpleToken(vsCloseRoundBracket)); '[': @@ -596,6 +588,15 @@ function TTemplateLexer.GetTextToken: ITemplateSymbol; begin Result := ValueToken(vsText); case FLookahead.Input of + '#': + begin + SwallowInput; + while not FLookahead.Eof and not((FCurrent.Input = FEndScript[1]) and Expecting(FEndScript[2])) do + SwallowInput; + SwallowInput; + FNextToken.enqueue(SimpleToken(vsComment)); + exit; + end; // '_': // LState := TStripAction.saUnindent; '-': diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 20af3d2..b93473d 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -1050,6 +1050,10 @@ function TTemplateParser.RuleStmts(Container: ITemplate; const AEndToken: TTempl begin LStmt := nil; case LSymbol.Token of + vsComment: + begin + FLookahead := FLexer.GetToken; + end; vsWhiteSpace, vsNewLine, vsText: begin LStmt := AddPrintStmt; diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 237f653..ababced 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -49,8 +49,6 @@ TTestTemplate = class [Test] procedure TestNonStmt; [Test] - procedure TestComment; - [Test, Ignore] procedure TestHashComment; [Test {$IFNDEF SEMPARE_TEMPLATE_HAS_HTML_ENCODER}, Ignore{$ENDIF}] procedure TestHtmlEncoding; @@ -147,15 +145,6 @@ procedure TTestTemplate.TestArray; Assert.AreEqual('2', Template.Eval('<% a:= [1,''hello world'', 2] %><% a[2]%>')); end; -procedure TTestTemplate.TestComment; -begin - Assert.AreEqual('before after ', Template.Eval( // - 'before ' + // - '<% (* this is '#13#10#13#10'a comment *) %>' + // - 'after ' // - )); -end; - procedure TTestTemplate.TestHashComment; begin Assert.AreEqual('before after ', Template.Eval( // From f248049e811eb674ef6155bf61b3b3d2fa14da79 Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Tue, 11 Apr 2023 23:55:01 +0100 Subject: [PATCH 107/138] Add caption showing cursor position --- .../Sempare.Template.DemoForm.dfm | 51 +++++++++++++++++-- .../Sempare.Template.DemoForm.pas | 20 ++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm index 567b68e..6c00810 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.dfm +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.dfm @@ -327,17 +327,58 @@ object FormRealTime: TFormRealTime ExplicitHeight = 252 object tsTemplate: TTabSheet Caption = 'Template' - object memoTemplate: TMemo + object Panel2: TPanel Left = 0 Top = 0 Width = 456 Height = 225 Align = alClient - ScrollBars = ssBoth + BevelOuter = bvNone + Caption = 'Panel2' TabOrder = 0 - WantTabs = True - OnChange = memoTemplateChange - ExplicitHeight = 224 + ExplicitLeft = 56 + ExplicitWidth = 185 + ExplicitHeight = 41 + object memoTemplate: TMemo + Left = 0 + Top = 17 + Width = 456 + Height = 208 + Align = alClient + ScrollBars = ssBoth + TabOrder = 0 + WantTabs = True + OnChange = memoTemplateChange + OnKeyUp = memoTemplateKeyUp + OnMouseDown = memoTemplateMouseDown + ExplicitTop = 0 + ExplicitHeight = 225 + end + object Panel3: TPanel + Left = 0 + Top = 0 + Width = 456 + Height = 17 + Align = alTop + Caption = 'Panel3' + ShowCaption = False + TabOrder = 1 + object lblPosition: TLabel + AlignWithMargins = True + Left = 4 + Top = 4 + Width = 448 + Height = 17 + Align = alTop + Alignment = taRightJustify + AutoSize = False + Caption = '(Line: 1, Position: 1)' + Transparent = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 456 + end + end end end object tsPrettyPrint: TTabSheet diff --git a/demo/VelocityDemo/Sempare.Template.DemoForm.pas b/demo/VelocityDemo/Sempare.Template.DemoForm.pas index 24ad8f7..e70bc22 100644 --- a/demo/VelocityDemo/Sempare.Template.DemoForm.pas +++ b/demo/VelocityDemo/Sempare.Template.DemoForm.pas @@ -100,6 +100,9 @@ TFormRealTime = class(TForm) cbUseCustomScriptTags: TCheckBox; cbFlattenTemplate: TCheckBox; cbShowWhitespace: TCheckBox; + lblPosition: TLabel; + Panel2: TPanel; + Panel3: TPanel; procedure cbConvertTabsToSpacesClick(Sender: TObject); procedure cbStripRecurringSpacesClick(Sender: TObject); procedure cbTrimLinesClick(Sender: TObject); @@ -125,6 +128,8 @@ TFormRealTime = class(TForm) procedure cbFlattenTemplateClick(Sender: TObject); procedure cmbCustomScriptTagsChange(Sender: TObject); procedure cbShowWhitespaceClick(Sender: TObject); + procedure memoTemplateMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + procedure memoTemplateKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } FEncoding: TEncoding; @@ -440,6 +445,21 @@ procedure TFormRealTime.memoTemplateChange(Sender: TObject); butEvalClick(Sender); end; +function GetRowCol(const AMemo: TMemo): string; +begin + exit(format('(Line: %d, Position: %d) ', [AMemo.CaretPos.Y + 1, AMemo.CaretPos.X + 1])); +end; + +procedure TFormRealTime.memoTemplateKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); +begin + lblPosition.Caption := GetRowCol(memoTemplate); +end; + +procedure TFormRealTime.memoTemplateMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +begin + lblPosition.Caption := GetRowCol(memoTemplate); +end; + procedure TFormRealTime.OnException(Sender: TObject; E: Exception); begin end; From 3b0b6a8db3891a132385eb2cb15b0a41d522e6ad Mon Sep 17 00:00:00 2001 From: Conrad Vermeulen Date: Wed, 12 Apr 2023 10:08:09 +0100 Subject: [PATCH 108/138] set target on a href --- demo/WebBrokerStandalone/templates/index.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/WebBrokerStandalone/templates/index.tpl b/demo/WebBrokerStandalone/templates/index.tpl index 5212c64..1a3bd17 100644 --- a/demo/WebBrokerStandalone/templates/index.tpl +++ b/demo/WebBrokerStandalone/templates/index.tpl @@ -16,7 +16,7 @@

    This is the <% include('resolve_current_demo') %> Demo.

    Also have a look at other demos, such as: <% for demo of _ %> -

  • <% demo.Name %> Demo<% if demo.Current %> (Current)<% end %> +
  • <% demo.Name %> Demo<% if demo.Current %> (Current)<% end %> <% onbegin %>