diff --git a/boss.json b/boss.json index 34da32e..671b52f 100644 --- a/boss.json +++ b/boss.json @@ -1,7 +1,7 @@ { "name": "Sempare Template Engine", "description": "Sempare Template Engine for Delphi allows for flexible text manipulation. It can be used for generating email, html, source code, xml, configuration, etc.", - "version": "1.6.0", + "version": "1.6.2", "homepage": "https://github.com/sempare/sempare-delphi-template-engine", "mainsrc": "./src/", "projects": [], diff --git a/docs/configuration.md b/docs/configuration.md index 4af5e7d..5aee343 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,6 +15,8 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) - [Dynamic Template Resolution](#Dynamic_Template_Resolution) - [Ignoring Whitespace With Multi-Line Statements](#Ignoring_Whitespace_With_Multi_Line_Statements) - [Options](#Options) +- [Decimal Separators](#Decimal_Separators) +- [Value Separators](#Value_Separators) # Overview @@ -159,3 +161,30 @@ The template engine allows for the following options: - strip any empty lines - eoPrettyPrint - use to review the parsed structure. output is to the console. + +### Decimal Separators + +Numbers are commonly formatted using comma and decimal point. e.g. 123.45 + +However, in some regions, such as Germany, it the coma may be preferred. e.g. 123,45 + +In order to accomodate this, the context configuration has a DecimalSeparator. These default based on locale. + +The DecimalSeparator may be set to '.' or ','. + +### Value Separators + +The ValueSeparator may be set to ',' or ';'. It must be explicity set. + +The motivation for the behaviour is to avoid any confusion with decimal separators. +``` +<% Add(1.23 , 4.56) %> +``` +When the DecimalSeparator is ',', then the ValueSeparator becomes ';' as illustrated: +``` +<% Add(1,23 ; 4,56) %> +``` +However, the following does work: +``` +<% Add(1,23 , 4,56) %> +``` \ No newline at end of file diff --git a/docs/debugging.md b/docs/debugging.md index d973544..62b3e53 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -10,3 +10,11 @@ writeln(Template.PrettyPrint(Template.Parse('<%if true%>true<%else%>false<%end%> ``` Use the _eoPrettyPrint_ option on the _context_ to enable when evaluating or parsing a template. + +## Strange parser errors + +Parser errors could come from one of two locations: +- the lexer - the code that breaks the stream into tokens +- the parser - the code that validates that the tokens are in the correct order according to the gramatical rules + + diff --git a/docs/images/stmt_assign.svg b/docs/images/stmt_assign.svg new file mode 100644 index 0000000..661f1fd --- /dev/null +++ b/docs/images/stmt_assign.svg @@ -0,0 +1,100 @@ + diff --git a/docs/images/stmt_block.svg b/docs/images/stmt_block.svg new file mode 100644 index 0000000..e69de29 diff --git a/docs/images/stmt_cycle.svg b/docs/images/stmt_cycle.svg new file mode 100644 index 0000000..953fc48 --- /dev/null +++ b/docs/images/stmt_cycle.svg @@ -0,0 +1,148 @@ + diff --git a/docs/images/stmt_extends.svg b/docs/images/stmt_extends.svg new file mode 100644 index 0000000..a7b8f6f --- /dev/null +++ b/docs/images/stmt_extends.svg @@ -0,0 +1,178 @@ + diff --git a/docs/images/stmt_for_in.svg b/docs/images/stmt_for_in.svg new file mode 100644 index 0000000..85aaa13 --- /dev/null +++ b/docs/images/stmt_for_in.svg @@ -0,0 +1,245 @@ + diff --git a/docs/images/stmt_for_in_event.svg b/docs/images/stmt_for_in_event.svg new file mode 100644 index 0000000..77f354d --- /dev/null +++ b/docs/images/stmt_for_in_event.svg @@ -0,0 +1,240 @@ + diff --git a/docs/images/stmt_for_range.svg b/docs/images/stmt_for_range.svg new file mode 100644 index 0000000..e6d8d76 --- /dev/null +++ b/docs/images/stmt_for_range.svg @@ -0,0 +1,281 @@ + diff --git a/docs/images/stmt_for_range_event.svg b/docs/images/stmt_for_range_event.svg new file mode 100644 index 0000000..0054ce8 --- /dev/null +++ b/docs/images/stmt_for_range_event.svg @@ -0,0 +1,293 @@ + diff --git a/docs/images/stmt_if.svg b/docs/images/stmt_if.svg new file mode 100644 index 0000000..a663770 --- /dev/null +++ b/docs/images/stmt_if.svg @@ -0,0 +1,241 @@ + diff --git a/docs/images/stmt_ignorenl.svg b/docs/images/stmt_ignorenl.svg new file mode 100644 index 0000000..dd00139 --- /dev/null +++ b/docs/images/stmt_ignorenl.svg @@ -0,0 +1,120 @@ + diff --git a/docs/images/stmt_include.svg b/docs/images/stmt_include.svg new file mode 100644 index 0000000..70565ea --- /dev/null +++ b/docs/images/stmt_include.svg @@ -0,0 +1,178 @@ + diff --git a/docs/images/stmt_print.svg b/docs/images/stmt_print.svg new file mode 100644 index 0000000..125e0a3 --- /dev/null +++ b/docs/images/stmt_print.svg @@ -0,0 +1,108 @@ + diff --git a/docs/images/stmt_print_expr.svg b/docs/images/stmt_print_expr.svg new file mode 100644 index 0000000..9fc1532 --- /dev/null +++ b/docs/images/stmt_print_expr.svg @@ -0,0 +1,84 @@ + \ No newline at end of file diff --git a/docs/images/stmt_require.svg b/docs/images/stmt_require.svg new file mode 100644 index 0000000..5594848 --- /dev/null +++ b/docs/images/stmt_require.svg @@ -0,0 +1,189 @@ + diff --git a/docs/images/stmt_template.svg b/docs/images/stmt_template.svg new file mode 100644 index 0000000..2c96b37 --- /dev/null +++ b/docs/images/stmt_template.svg @@ -0,0 +1,133 @@ + diff --git a/docs/images/stmt_while.svg b/docs/images/stmt_while.svg new file mode 100644 index 0000000..049e0b7 --- /dev/null +++ b/docs/images/stmt_while.svg @@ -0,0 +1,229 @@ + diff --git a/docs/images/stmt_while_event.svg b/docs/images/stmt_while_event.svg new file mode 100644 index 0000000..3e43530 --- /dev/null +++ b/docs/images/stmt_while_event.svg @@ -0,0 +1,224 @@ + diff --git a/docs/images/stmt_with.svg b/docs/images/stmt_with.svg new file mode 100644 index 0000000..d763bbe --- /dev/null +++ b/docs/images/stmt_with.svg @@ -0,0 +1,124 @@ + diff --git a/docs/rail-road-diagrams.txt b/docs/rail-road-diagrams.txt new file mode 100644 index 0000000..e96a5a0 --- /dev/null +++ b/docs/rail-road-diagrams.txt @@ -0,0 +1,541 @@ +https://tabatkins.github.io/railroad-diagrams/generator.html +https://github.com/tabatkins/railroad-diagrams/blob/gh-pages/README-js.md + +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20NonTerminal('var')%2C%0A%20%20%20%20%20%20%20Terminal('%3A%3D')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20)%0A) + + +Diagram( +Sequence( + Terminal('<%'), + NonTerminal('var'), + Terminal(':='), + NonTerminal('expr'), + Terminal('%>'), + ) +) + +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('for')%2C%0A%20%20%20%20%20%20%20NonTerminal('var')%2C%0A%20%20%20%20%20%20%20Terminal('%3A%3D')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Choice(0%2C%0A%20%20%20%20%20%20%20%20%20Terminal('to')%2C%0A%20%20%20%20%20%20%20%20%20Terminal('downto'))%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('step')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('offset')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('limit')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20OneOrMore(%0A%20%20%20%20%20%20%20%20%20Choice(0%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20continue%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20break%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%0A%20%20)%0A) + +Diagram( + Stack( + Stack( + Sequence( + Terminal('<%'), + Terminal('for'), + NonTerminal('var'), + Terminal(':='), + NonTerminal('expr'), + Choice(0, + Terminal('to'),Terminal('downto'),), + NonTerminal('expr'), + ), + Sequence( + ZeroOrMore( + Sequence( + Choice(0, + Terminal('step'), + Terminal('offset'), + Terminal('limit') + ), + NonTerminal('expr') + ) + ), + Terminal('%>'), OneOrMore( + Choice(0, + NonTerminal('block'), + Terminal('<% continue %>'), + Terminal('<% break %>'), + ) + ), + + ) + ), + + Sequence(Terminal('<%'),Terminal('end'),Terminal('%>')) + ) +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0A%20Sequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('for')%2C%0A%20%20%20%20%20%20%20NonTerminal('var')%2C%0A%20%20%20%20%20%20%20Terminal('in')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('offset')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('limit')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20OneOrMore(%0A%20%20%20%20%20%20%20%20%20Choice(0%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20continue%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20break%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%0A%20%20)%0A) + +Diagram( + Stack( + Stack( + Sequence( + Terminal('<%'), + Terminal('for'), + NonTerminal('var'), + Terminal('in'), + NonTerminal('expr'), + ), + Sequence( + ZeroOrMore( + Sequence( + Choice(0, + Terminal('offset'), + Terminal('limit') + ), + NonTerminal('expr') + ) + ), + Terminal('%>'), OneOrMore( + Choice(0, + NonTerminal('block'), + Terminal('<% continue %>'), + Terminal('<% break %>'), + ) + ), + + ) + ), + + Sequence(Terminal('<%'),Terminal('end'),Terminal('%>')) + ) +) + +--------------------- + + +Diagram( + Stack( + + Sequence( + Terminal('<%'), + Terminal('for'), + NonTerminal('var'), + Terminal('in'), + NonTerminal('expr'), + Terminal('%>'), + NonTerminal('block'), + ), + ZeroOrMore( + Choice(0, + + Sequence( + Terminal('<%'), + Choice(0, + Terminal('onbegin'), + Terminal('onend'), + Terminal('betweenitems'), + Terminal('onempty'), + ), + Terminal('%>'), + NonTerminal('block'), + ) , + + ),), + + + + +Sequence(Terminal('<%'),Terminal('end'),Terminal('%>'), +) + ) +) + +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('while')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('offset')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(Terminal('limit')%2C%20%20NonTerminal('expr'))%0A%20%20%20%20%20%20%20)%2C%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20OneOrMore(%0A%20%20%20%20%20%20%20%20%20Choice(0%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20continue%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20break%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%0A%20%20)%0A) + +Diagram( + Stack( + Stack( + Sequence( + Terminal('<%'), + Terminal('while'), + NonTerminal('expr'), + ), + Sequence( + ZeroOrMore( + Sequence( + Choice(0, + Terminal('offset'), + Terminal('limit'), + ), + NonTerminal('expr'), + )), + Terminal('%>'), +OneOrMore( + Choice(0, + NonTerminal('block'), + Terminal('<% continue %>'), + Terminal('<% break %>'), + ) + ), + + + ), + + + ), + + +Sequence(Terminal('<%'),Terminal('end'),Terminal('%>'), +) + ) +) + +---------------------- + +Diagram( + Stack( + + Sequence( + Terminal('<%'), + Terminal('while'), + NonTerminal('expr'), + Terminal('%>'), + NonTerminal('block'), + ), + + Optional( + Choice(0, + + Sequence( + Terminal('<%'), + Terminal('onbegin'), + Terminal('%>'), + NonTerminal('block'), + ) , + + Sequence( + Terminal('<%'), + Terminal('onend'), + Terminal('%>'), + NonTerminal('block'), + ), + + Sequence( + Terminal('<%'), + Terminal('betweenitems'), + Terminal('%>'), + NonTerminal('block'), + ), + + + Sequence( + Terminal('<%'), + Terminal('onempty'), + Terminal('%>'), + NonTerminal('block'), + ), + + ),), + + + + +Sequence(Terminal('<%'),Terminal('end'),Terminal('%>'), +) + ) +) + + + +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('if')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20elif%20')%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%3C%25%20else%20%25%3E')%2C%0A%20%20%20%20%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%0A%20%20)%0A) + +Diagram( + + Stack( + Sequence( + Terminal('<%'), + Terminal('if'), + NonTerminal('expr'), + Terminal('%>'), + NonTerminal('block'), + ), + ZeroOrMore( + Sequence( + Terminal('<%'),Terminal('elif'), + NonTerminal('expr'), + Terminal('%>'), + NonTerminal('block'), + ), + ), + Optional( + Sequence( + Terminal('<%'),Terminal('else'),Terminal('%>'), + NonTerminal('block'), + ), + ) + ), + Terminal('<%'),Terminal('end'),Terminal('%>'), + +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('include')%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%20Optional(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal(')')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20)%0A) +Diagram( +Sequence( + Terminal('<%'), + Terminal('include'), + Terminal('('), + NonTerminal('expr'), + Optional( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), + ) +) + + +Diagram( + Stack( +Sequence( + Terminal('<%'), + Terminal('include'), + Terminal('('), + NonTerminal('expr'), + Optional( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), + + ), + +Sequence( NonTerminal('block'), + Terminal('<%'),Terminal('end'),Terminal('%>'), +) +) +) + + +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('extends')%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%20Optional(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal(')')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%20%0A%20%20%20)%0A) + +Diagram( + Stack( +Sequence( + Terminal('<%'), + Terminal('extends'), + Terminal('('), + NonTerminal('expr'), + Optional( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), + + ), + NonTerminal('block'), +Sequence( + Terminal('<%'),Terminal('end'),Terminal('%>'), +) +) +) + +Diagram( + Stack( +Sequence( + Terminal('<%'), + Terminal('extends'), + Terminal('('), + NonTerminal('expr'), + Optional( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), + + ), + +Sequence( NonTerminal('block'), + Terminal('<%'),Terminal('end'),Terminal('%>'), +) +) +) + + +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('with')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%20%20%0A%20%20%20%20%20%20%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%20%0A%20%20%20)%0A) + +Diagram( +Sequence( + Terminal('<%'), + Terminal('with'), + NonTerminal('expr'), + + Terminal('%>'), + NonTerminal('block'), + Terminal('<%'),Terminal('end'),Terminal('%>'), + ) +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('template')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%20%20%0A%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%20%0A%20%20%20)%0A) + +Diagram( + Stack( +Sequence( + Terminal('<%'), + Terminal('template'), + NonTerminal('expr'), + Terminal('%>'), + + ), + +Sequence( NonTerminal('block'), + Terminal('<%'),Terminal('end'),Terminal('%>'), +) +) +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('require')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%20%20%0A%20%20%20%20%20%20%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%20%0A%20%20%20)%0A) + +Diagram( +Stack( +Sequence( + Terminal('<%'), + Terminal('require'), + Terminal('('), + NonTerminal('expr'), + ZeroOrMore( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), +), +Sequence( + NonTerminal('block'), + Terminal('<%'), Terminal('end'), Terminal('%>'), + ) +) +) +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25%20ignorenl%20%25%3E')%2C%0A%20%20%20%20%20%20%20NonTerminal('block')%2C%0A%20%20%20%20%20%20%20Terminal('%3C%25%20end%20%25%3E')%2C%20%0A%20%20%20)%0A) + +Diagram( +Sequence( + Terminal('<%'), + Terminal('ignorenl'), + Terminal('%>'), + + ), + NonTerminal('block'), +Sequence( + Terminal('<%'),Terminal('end'),Terminal('%>'), +) +) +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('cycle')%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%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%2C')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20)%2C%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20Terminal(')')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%20%20%20)%0A) + +Diagram( +Sequence( + Terminal('<%'), + Terminal('cycle'), + Terminal('('), + NonTerminal('expr'), + ZeroOrMore( + Sequence( + Terminal(','), + NonTerminal('expr'), + ) + ), + Terminal(')'), + Terminal('%>'), + ) +) +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ATerminal('_')%2C%0A) + +Diagram( +Terminal('_'), +) +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%5C'')%2C%0A%20%20%20%20%20%20%20%20%20%20%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%5Ba..zA..Z0..9...%5D')%2C%0A%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%5C'')%0A%20%20%20%20%20)%2C%0A) + +Diagram(Sequence( + Terminal('\''), + ZeroOrMore( + Terminal('[a..zA..Z0..9...]'), + ), + Terminal('\'') + ), +) +------------------------- + +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%22')%2C%0A%20%20%20%20%20%20%20%20%20%20%20ZeroOrMore(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20Terminal('%5Ba..zA..Z0..9...%5D')%0A%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20Terminal('%22')%0A%20%20%20%20%20)%2C%0A) + +Diagram(Sequence( + Terminal('"'), + ZeroOrMore( + Terminal('[a..zA..Z0..9...]') + ), + Terminal('"') + ), +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20%20OneOrMore('%5B0..9%5D')%2C%0A%20%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Terminal('.')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20OneOrMore('%5B0..9%5D')%2C%0A%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20%20Sequence(%0A%20%20%20%20%20%20%20%20%20%20%20%20Choice(0%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20'e'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20'E'%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Optional(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20Choice(0%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'%2B'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'-'%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20OneOrMore('%5B0..9%5D')%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20)%0A) +Diagram( +Sequence( + OneOrMore('[0..9]'), + Optional( + Sequence( + Terminal('.'), + OneOrMore('[0..9]'), + ), + ), + Optional( + Sequence( + Choice(0, + 'e', + 'E' + ), + Optional( + Choice(0, + '+', + '-' + ) + ), + OneOrMore('[0..9]'), + ) + ) + ) +) +------------------------- +Diagram( +) +------------------------- +Diagram( +) +------------------------- +Diagram( +) +------------------------- +https://tabatkins.github.io/railroad-diagrams/generator.html#Diagram(%0ASequence(%0A%20%20%20%20%20%20%20Terminal('%3C%25')%2C%0A%20%20%20%20%20%20%20Terminal('print')%2C%0A%20%20%20%20%20%20%20NonTerminal('var')%2C%0A%20%20%20%20%20%20%20Terminal('(')%2C%0A%20%20%20%20%20%20%20NonTerminal('expr')%2C%0A%20%20%20%20%20%20%20Terminal(')')%2C%0A%20%20%20%20%20%20%20Terminal('%25%3E')%2C%0A%0A%20%20)%0A) +Diagram( +Sequence( + Terminal('<%'), + Terminal('print'), + NonTerminal('var'), + Terminal('('), + NonTerminal('expr'), + Terminal(')'), + Terminal('%>'), + + ) +) \ No newline at end of file diff --git a/docs/statements.md b/docs/statements.md index 9e4ff4a..799d061 100644 --- a/docs/statements.md +++ b/docs/statements.md @@ -18,6 +18,10 @@ Copyright (c) 2019-2023 [Sempare Limited](http://www.sempare.ltd) ### print +![print expr](./images/stmt_print_expr.svg) + +![print](./images/stmt_print.svg) + Within a script, all text outside of the script _'<%'_ start and _'%>'_ end tokens is output. ``` @@ -32,7 +36,16 @@ This <% stmt %> test. ``` The above example results in _'this is a test.'_ being output. +You may also rely on the print() statement. + +``` +<% print('this is a test') %> +``` + ### assignment + +![assign](./images/stmt_assign.svg) + Within a script block, you can create temporary variables for use within the template. Basic types such a string, boolean and numbers can be used. ``` <% num := 123 %> @@ -41,6 +54,8 @@ Within a script block, you can create temporary variables for use within the tem ``` ### if +![if](./images/stmt_if.svg) + You may want to conditionally include content: ``` Some text @@ -75,6 +90,8 @@ the list as at least one item ### for +![for range](./images/stmt_for_range.svg) + The _for to/downto_ loop comes in two forms as it does in Object Pascal: ``` increasing integers from 1 to 10 @@ -97,6 +114,8 @@ You can change the _step_ interval as follows: <% end %> ``` +![for in](./images/stmt_for_in.svg) + The other _for in_ variation is as follows: ``` Attendees: @@ -113,6 +132,8 @@ Attendees: <%end%> ``` +![for in event](./images/stmt_for_in_event.svg) + Further, there are _onbegin_, _onend_, _betweenitems_ and _onempty_ events that can be used. ``` Attendees: @@ -192,6 +213,8 @@ Using for-in on TDataSet, the loop variable enumerates rows. The loop variable c ### while +![while](./images/stmt_while.svg) + While blocks are very flexibe looping constructs based on a boolean condition being true. ``` @@ -219,6 +242,9 @@ While loops may also have _offset_ and _limits_ defined: <% i := i + 1%> <% end %> ``` + +![while events](./images/stmt_while_event.svg) + While loops may also have _onbegin_, _onend_, _betweenitems_ and _onempty_ events defined: ``` <% i := 1 %> @@ -264,6 +290,8 @@ This will produce ### include +![include](./images/stmt_include.svg) + You may want to decompose templates into reusable parts. You register templates on a Template context. ``` @@ -287,6 +315,8 @@ include() can also take a second parameter, allowing for improved scoping of var ### with +![with](./images/stmt_with.svg) + The with() statement is used to localise variables from a nested structure. To illustrate, the following @@ -319,6 +349,8 @@ The _with()_ statement will push all fields/properties in a record/class into a ### template +![template](./images/stmt_template.svg) + Localised templates can also be defined locally within a template. The _include_() statement is used to render a local template as is the normal behaviour. Note that local templates take precedence over templates defined in a context when they are being resolved. @@ -334,6 +366,8 @@ Using the TInfo structure above it could be appli ### require +![require](./images/stmt_require.svg) + The _require_() statement is used to validate that the input variable is of a particular type. e.g. @@ -356,6 +390,8 @@ _require_ can take multiple parameters - in which case the input must match one ### ignorenl +![ignorenl](./images/stmt_ignorenl.svg) + The purpose of an _ignorenl_ block is to allow for template designers to space out content for easy maintenance, but to allow for the output to be more compact when required. ``` @@ -383,6 +419,8 @@ The _eoAllowIgnoreNL_ must be provided in the Context.Options or via Template.Ev ### cycle +![cycle](./images/stmt_cycle.svg) + This allows for values to cycle when rendering content. e.g. ``` diff --git a/src/Sempare.Template.Context.pas b/src/Sempare.Template.Context.pas index ca52d18..e32aa0c 100644 --- a/src/Sempare.Template.Context.pas +++ b/src/Sempare.Template.Context.pas @@ -134,6 +134,7 @@ interface procedure SetScriptEndStripToken(const Value: string); procedure SetScriptStartStripToken(const Value: string); + procedure SetValueSeparator(const ASeparator: char); function GetValueSeparator: char; function GetDecimalSeparator: char; procedure SetDecimalSeparator(const ASeparator: char); @@ -158,7 +159,7 @@ interface property StartStripToken: string read GetScriptStartStripToken write SetScriptStartStripToken; property EndStripToken: string read GetScriptEndStripToken write SetScriptEndStripToken; - property ValueSeparator: char read GetValueSeparator; + property ValueSeparator: char read GetValueSeparator write SetValueSeparator; property DecimalSeparator: char read GetDecimalSeparator write SetDecimalSeparator; property FormatSettings: TFormatSettings read GetFormatSettings; property DebugErrorFormat: string read GetDebugErrorFormat write SetDebugErrorFormat; @@ -286,6 +287,7 @@ TTemplateContext = class(TInterfacedObject, ITemplateContext, ITemplateContext function GetFormatSettings: TFormatSettings; + procedure SetValueSeparator(const ASeparator: char); procedure SetDecimalSeparator(const ASeparator: char); function GetDebugErrorFormat: string; @@ -479,10 +481,6 @@ procedure TTemplateContext.SetDecimalSeparator(const ASeparator: char); if not(FFormatSettings.DecimalSeparator in ['.', ',']) then raise ETemplate.CreateRes(@SDecimalSeparatorMustBeACommaOrFullStop); {$WARN WIDECHAR_REDUCED ON} - if FFormatSettings.DecimalSeparator = '.' then - FValueSeparator := ',' - else - FValueSeparator := ';'; end; procedure TTemplateContext.SetEncoding(const AEncoding: TEncoding); @@ -512,6 +510,15 @@ procedure TTemplateContext.SetOptions(const AOptions: TTemplateEvaluationOptions FOptions := AOptions; end; +procedure TTemplateContext.SetValueSeparator(const ASeparator: char); +begin +{$WARN WIDECHAR_REDUCED OFF} + if not(ASeparator in [',', ';']) then + raise ETemplate.CreateRes(@SDecimalSeparatorMustBeACommaOrFullStop); +{$WARN WIDECHAR_REDUCED ON} + FValueSeparator := ASeparator; +end; + procedure TTemplateContext.SetVariable(const AName: string; const AValue: TValue); begin FLock.Enter; diff --git a/src/Sempare.Template.Parser.pas b/src/Sempare.Template.Parser.pas index 184f860..75ecb41 100644 --- a/src/Sempare.Template.Parser.pas +++ b/src/Sempare.Template.Parser.pas @@ -474,6 +474,9 @@ TTemplateParser = class(TInterfacedObject, ITemplateParser) function ruleFunctionExpr(const ASymbol: string): IExpr; function ruleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr; function ruleRequireStmt: IStmt; + + function GetValueSeparatorSymbol: TTemplateSymbol; + procedure CheckForOppositeValueSeparator(const AExpect: TTemplateSymbol); public constructor Create(AContext: ITemplateContext); destructor Destroy; override; @@ -543,6 +546,32 @@ function GetTemplateParser(AContext: ITemplateContext): ITemplateParser; end; { TTemplateParser } +const + ValueSeparators = [vsComma, vsSemiColon]; + +function OppositeValueSepartor(const ASep: TTemplateSymbol): TTemplateSymbol; +begin + if ASep = vsComma then + exit(vsSemiColon) + else + exit(vsComma); +end; + +function TTemplateParser.GetValueSeparatorSymbol: TTemplateSymbol; +begin + if FContext.ValueSeparator = ';' then + exit(vsSemiColon) + else + exit(vsComma); +end; + +procedure TTemplateParser.CheckForOppositeValueSeparator(const AExpect: TTemplateSymbol); + +begin + if FLookahead.Token in ValueSeparators then + match(OppositeValueSepartor(FLookahead.Token)); + match(AExpect); +end; constructor TTemplateParser.Create(AContext: ITemplateContext); begin @@ -636,19 +665,21 @@ function TTemplateParser.ruleIncludeStmt: IStmt; LIncludeExpr: IExpr; LScopeExpr: IExpr; LContainerTemplate: TTemplate; + LValueSeparator: TTemplateSymbol; begin LSymbol := FLookahead; match(vsInclude); match(vsOpenRoundBracket); LIncludeExpr := ruleExpression; - if FLookahead.Token = vsComma then + LValueSeparator := GetValueSeparatorSymbol; + if FLookahead.Token = LValueSeparator then begin - match(vsComma); + match(LValueSeparator); LScopeExpr := ruleExpression; end; - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); match(vsEndScript); if LScopeExpr <> nil then @@ -671,7 +702,7 @@ function TTemplateParser.ruleRequireStmt: IStmt; match(vsRequire); match(vsOpenRoundBracket); result := TRequireStmt.Create(LSymbol.Position, self.ruleExprList()); - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); match(vsEndScript); end; @@ -682,7 +713,7 @@ function TTemplateParser.ruleMethodExpr(AExpr: IExpr; AMethodExpr: IExpr): IExpr LSymbol := FLookahead; match(vsOpenRoundBracket); result := TMethodCallExpr.Create(LSymbol.Position, AExpr, AsVarString(AMethodExpr), ruleExprList); - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); end; function TTemplateParser.ruleStmts(Container: ITemplate; const AEndToken: TTemplateSymbolSet): TTemplateSymbol; @@ -1019,7 +1050,7 @@ function TTemplateParser.ruleCycleStmt: IStmt; LListExpr := ruleExprList(); - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); match(vsEndScript); exit(TCycleStmt.Create(LSymbol.Position, LListExpr)); @@ -1141,6 +1172,7 @@ function TTemplateParser.ruleExpression: IExpr; function TTemplateParser.ruleFactor: IExpr; var LSymbol: ITemplateSymbol; + begin LSymbol := FLookahead; case LSymbol.Token of @@ -1148,14 +1180,14 @@ function TTemplateParser.ruleFactor: IExpr; begin match(vsOpenSquareBracket); result := TArrayExpr.Create(LSymbol.Position, ruleExprList(vsCloseSquareBracket)); - match(vsCloseSquareBracket); + CheckForOppositeValueSeparator(vsCloseSquareBracket); exit; end; vsOpenRoundBracket: begin match(vsOpenRoundBracket); result := ruleExpression; - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); exit; end; vsString: @@ -1307,7 +1339,7 @@ function TTemplateParser.ruleFunctionExpr(const ASymbol: string): IExpr; RaiseError(LSymbol.Position, SFunctionNotRegisteredInContext, [ASymbol]); match(vsOpenRoundBracket); result := TFunctionCallExpr.Create(LSymbol.Position, LFunctions, ruleExprList); - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); end; function TTemplateParser.ruleIdStmt: IStmt; @@ -1455,7 +1487,7 @@ function TTemplateParser.rulePrintStmt: IStmt; match(vsPrint); match(vsOpenRoundBracket); LExpr := ruleExpression; - match(vsCloseRoundBracket); + CheckForOppositeValueSeparator(vsCloseRoundBracket); match(vsEndScript); exit(TPrintStmt.Create(LSymbol.Position, LExpr)); end; @@ -1480,10 +1512,7 @@ function TTemplateParser.ruleExprList(const AEndToken: TTemplateSymbol): IExprLi result := TExprList.Create(LSymbol.Position); if FLookahead.Token <> AEndToken then result.AddExpr(ruleExpression); - if FContext.ValueSeparator = ';' then - LValueSeparator := vsSemiColon - else - LValueSeparator := vsComma; + LValueSeparator := GetValueSeparatorSymbol; while FLookahead.Token = LValueSeparator do begin match(LValueSeparator); diff --git a/tests/Sempare.Template.Test.pas b/tests/Sempare.Template.Test.pas index 1f3a963..c2a1846 100644 --- a/tests/Sempare.Template.Test.pas +++ b/tests/Sempare.Template.Test.pas @@ -65,6 +65,8 @@ TTestTemplate = class [Test] procedure TestVariableNotFound; [Test] + procedure TestVariableNotFoundException; + [Test] procedure TestArray; [Test, Ignore] // This is ignored because this is a potential future feature that is not currently supported. @@ -104,6 +106,18 @@ TTestTemplate = class [Test] procedure TestException; + + [Test] + procedure TestDecimalEncodingErrorWithLists; + + [Test] + procedure TestDecimalEncodingErrorWithParameters; + + [Test] + procedure TestValueSeparatorSameAsDecimalSeparator; + + [Test] + procedure TestDecimalEncodingErrorWithListsDefaultValueSeparator; end; type @@ -389,6 +403,63 @@ procedure TTestTemplate.TestTabToSpaceAndNoSpace; Assert.AreEqual(' hello world', Template.Eval(ctx, #9' hello '#9' world')); end; +procedure TTestTemplate.TestDecimalEncodingErrorWithLists; +var + ctx: ITemplateContext; +begin + ctx := Template.Context; + ctx.DecimalSeparator := ','; + ctx.ValueSeparator := ';'; + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a"; "b"] %>')); + Assert.WillRaise( + procedure + begin // expecting ; + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a", "b"] %>')); + end); + ctx.DecimalSeparator := '.'; + ctx.ValueSeparator := ','; + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a", "b"] %>')); + Assert.WillRaise( + procedure + begin // expecting , + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a"; "b"] %>')); + end); +end; + +procedure TTestTemplate.TestDecimalEncodingErrorWithListsDefaultValueSeparator; +var + ctx: ITemplateContext; +begin + ctx := Template.Context; + ctx.DecimalSeparator := ','; + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a", "b"] %>')); + ctx.DecimalSeparator := '.'; + Assert.AreEqual('', Template.Eval(ctx, '<% a := ["a", "b"] %>')); +end; + +procedure TTestTemplate.TestDecimalEncodingErrorWithParameters; +var + ctx: ITemplateContext; +begin + ctx := Template.Context; + ctx.DecimalSeparator := ','; + ctx.ValueSeparator := ';'; + Assert.AreEqual('a b', Template.Eval(ctx, '<% fmt("%s %s"; "a"; "b") %>')); + Assert.WillRaise( + procedure + begin // expecting ; + Assert.AreEqual('', Template.Eval(ctx, '<% fmt("%s %s", "a", "b") %>')); + end); + ctx.DecimalSeparator := '.'; + ctx.ValueSeparator := ','; + Assert.AreEqual('a b', Template.Eval(ctx, '<% fmt("%s %s", "a", "b") %>')); + Assert.WillRaise( + procedure + begin // expecting , + Assert.AreEqual('', Template.Eval(ctx, '<% fmt("%s %s"; "a"; "b") %>')); + end); +end; + procedure TTestTemplate.TestDynamicLoader; var ctx: ITemplateContext; @@ -462,11 +533,39 @@ procedure TTestTemplate.TestUnderscoreIn; end; end; +procedure TTestTemplate.TestValueSeparatorSameAsDecimalSeparator; +var + ctx: ITemplateContext; +begin + ctx := Template.Context; + ctx.DecimalSeparator := ','; + ctx.ValueSeparator := ','; + Assert.AreEqual('1,12 4,56 ', Template.Eval(ctx, '<% a := [ 1,12 , 4,56 ] %><% for i of a %><% i %> <% end %>')); + Assert.WillRaise( + procedure + begin // expects , + Assert.AreEqual('1,12 4,56 ', Template.Eval(ctx, '<% a := [ 1,12 ; 4,56 ] %><% for i of a %><% i %> <% end %>')); + end); +end; + procedure TTestTemplate.TestVariableNotFound; begin Assert.AreEqual('', Template.Eval('<% abc %>')); end; +procedure TTestTemplate.TestVariableNotFoundException; +var + LCtx: ITemplateContext; +begin + LCtx := Template.Context(); + LCtx.Options := LCtx.Options + [eoRaiseErrorWhenVariableNotFound]; + Assert.WillRaise( + procedure + begin // expects abc + Assert.AreEqual('', Template.Eval(LCtx, '<% abc %>')); + end); +end; + initialization TDUnitX.RegisterTestFixture(TTestTemplate); diff --git a/tests/Sempare.Template.TestFunctions.pas b/tests/Sempare.Template.TestFunctions.pas index e0e53b2..3b52c61 100644 --- a/tests/Sempare.Template.TestFunctions.pas +++ b/tests/Sempare.Template.TestFunctions.pas @@ -34,6 +34,8 @@ interface +{$I 'Sempare.Template.Compiler.inc'} + uses DUnitX.TestFramework; @@ -90,6 +92,12 @@ TFunctionTest = class [Test] procedure AddFmtNumTest; [Test] + procedure AddCustomFmt; + [Test] + procedure AddCustomFmt2; + [Test] + procedure AddCustomFmt3; + [Test] procedure TestIsNil; [Test] procedure TestProcedure; @@ -117,6 +125,10 @@ TMyType = class implementation uses +{$IFDEF SEMPARE_TEMPLATE_FIREDAC} + Data.DB, + FireDAC.Comp.Client, +{$ENDIF} System.SysUtils, System.Rtti, Sempare.Template.Functions, @@ -124,7 +136,121 @@ implementation Sempare.Template, Sempare.Template.Rtti; +{$IFDEF SEMPARE_TEMPLATE_FIREDAC} + +function CreateSendungen(): TFDMemTable; +var + ds: TFDMemTable; +begin + ds := TFDMemTable.Create(nil); + with ds do + begin + FieldDefs.Add('Spedition', ftWideString, 20); + FieldDefs.Add('Sendungsnummer', ftWideString, 20); + CreateDataSet; + end; + with ds do + begin + Append; + FieldByName('Spedition').value := 'blah'; + FieldByName('Sendungsnummer').value := '123'; + Post; + end; + exit(ds); +end; + +function CreateTracking(): TFDMemTable; +var + ds: TFDMemTable; +begin + ds := TFDMemTable.Create(nil); + with ds do + begin + FieldDefs.Add('Link', ftWideString, 100); + CreateDataSet; + end; + with ds do + begin + Append; + FieldByName('Link').value := 'https://abc.com/%s'; + Post; + end; + exit(ds); +end; + +{$ENDIF} { TFunctionTest } +{$IFDEF SEMPARE_TEMPLATE_FIREDAC} + +procedure TFunctionTest.AddCustomFmt2; +type + TData = record + Sendungen: TDataSet; + Tracking: TDataSet; + end; + +var + Data: TData; + ctx: ITemplateContext; + +begin + ctx := Template.Context(); + ctx.StartToken := '{{'; + ctx.EndToken := '}}'; + Data.Sendungen := nil; + Data.Tracking := nil; + try + Data.Sendungen := CreateSendungen; + Data.Tracking := CreateTracking; + Assert.AreEqual('