Skip to content

Commit

Permalink
Sort the function calls tree from left to right and from bottom to up
Browse files Browse the repository at this point in the history
  • Loading branch information
neveldo committed Sep 12, 2016
1 parent e4e0f86 commit 18bb92a
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 82 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ChangeLog
Change log for TextGenerator

## x.x.x
## 0.4.0 - September 12, 2016
- Sort the function calls tree from left to right and from bottom to up
- Add 'substring' filter to FilterFunction
- Fix error when a filter doesn't exist
- Fix set function when the variable name contains another existing variable name
Expand Down
14 changes: 0 additions & 14 deletions src/TextGenerator/Tag/TagReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,6 @@ public function sanitizeTagNames($content)
return strtr($content, $this->sanitizedTagNames);
}

/**
* Replace the tag $tagName by the matching value within the content
* @param string $content
* @param string $tagName, ex : '@tag_name'
* @return string
*/
public function replaceOne($content, $tagName)
{
if (isset($this->escapedTags[$tagName])) {
return str_replace($tagName, $this->escapedTags[$tagName], $content);
}
return $content;
}

/**
* Return a tag by its name
* @param $name ex : '@tag_name'
Expand Down
8 changes: 0 additions & 8 deletions src/TextGenerator/Tag/TagReplacerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@ public function replace($content);
*/
public function sanitizeTagNames($content);

/**
* Replace the tag $tagName by the matching value within the content
* @param string $content
* @param string $tagName
* @return string
*/
public function replaceOne($content, $tagName);

/**
* Return a tag by its name
* @param $name
Expand Down
113 changes: 56 additions & 57 deletions src/TextGenerator/TextGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ class TextGenerator
*/
private $statementsStack = [];

/**
* @var array sorted execution stack to run for generating a text
*/
private $sortedStatementsStack = [];


/**
* @var int execution stack size
*/
private $executionStackSize = 0;

/**
* TextGenerator constructor.
* @param TagReplacerInterface|null $tagReplacer
Expand Down Expand Up @@ -91,7 +102,7 @@ public function generate(array $data)

// Execute the functions stack starting with the deepest functions and ending
// with the shallowest ones
foreach($this->statementsStack as $statement) {
foreach($this->sortedStatementsStack as $statement) {

$openingTag = '[' . $statement['id'] . ']';
$closingTag = '[/' . $statement['id'] . ']';
Expand All @@ -113,12 +124,6 @@ public function generate(array $data)
strpos($text, $openingTag),
strpos($text, $closingTag) + strlen($closingTag) - strpos($text, $openingTag)
);

if ($statement['function'] === 'set') {
// After a tag affectation, parse this tag in all the text in order to replace
// Them with the proper value
$text = $this->tagReplacer->replaceOne($text, $parsedArguments[0]);
}
}

// Replace the remaining tags by the proper values
Expand All @@ -143,22 +148,26 @@ public function compile($template)

$this->compiledTemplate = $this->parseIndentations($template);
$this->compiledTemplate = $this->compileTemplate($this->compiledTemplate);
$this->sortStatements();

return $this;
}

// Sort the execution stack in order to execute the deepest
// functions first and end with the shallowest ones but preserving the order of
// calls that have the same depth level
uasort(
$this->statementsStack,
function($a, $b) {
if ($b['depth'] - $a['depth'] !== 0) {
return $b['depth'] - $a['depth'];
} else {
return $a['id'] - $b['id'];
}
/**
* Sort the function calls tree from left to right and from bottom to up
* @param $parent null|int parent statement ID
*/
public function sortStatements($parent = null)
{
foreach($this->statementsStack as $id => $options) {
if ($options['parent'] === $parent) {
$this->sortStatements($id);
}
);
}

return $this;
if ($parent !== null) {
$this->sortedStatementsStack[] = $this->statementsStack[$parent];
}
}

/**
Expand All @@ -175,48 +184,38 @@ public function parseIndentations($template)
/**
* Parse recursively the template to extract the execution stack
* @param string $template
* @param int|null $parent parent function ID
* @return string $template
* @throw \InvalidArgumentException if the template contains unknown functions
*/
protected function compileTemplate($template) {

$replacements = -1;
$depth = 0;
$statementsStackSize = 0;
while($replacements !== 0) {
$template = preg_replace_callback(
'/(#[a-z_]+\{(?:[^\{\}]|(?R))+\})/s',
function($template) use ($depth, &$statementsStackSize) {
// $template = '#fct{...}'
$template = $template[1];

// Add the function call into the execution stack
$functionName = substr($template, 1, strpos($template, '{') - 1);
if (!in_array($functionName, array_keys($this->functions))) {
throw new \InvalidArgumentException(sprintf("Error : function '%s' doesn't exist.", $functionName));
}
$this->statementsStack[$statementsStackSize] = [
'id' => $statementsStackSize,
'function' => $functionName,
'depth' => $depth
];

// Update the template to replace function calls by references to the execution stack like : [9]...[/9]
$template = substr_replace($template, '[' . $statementsStackSize . ']', 0, strpos($template, '{') + 1);
$template = substr_replace($template, '[/' . $statementsStackSize . ']', strlen($template) - 1);

++$statementsStackSize;

return $template;
},
$template,
-1,
$replacements
);
protected function compileTemplate($template, $parent = null) {
if (is_array($template)) {

++$depth;
// $template = '#fct{...}'
$template = $template[1];

// Add the function call into the execution stack
$functionName = substr($template, 1, strpos($template, '{') - 1);
if (!in_array($functionName, array_keys($this->functions))) {
throw new \InvalidArgumentException(sprintf("Error : function '%s' doesn't exist.", $functionName));
}
$this->statementsStack[] = ['id' => $this->executionStackSize, 'function' => $functionName, 'parent' => $parent];

// Update the template to replace function calls by references to the execution stack like : [9]...[/9]
$template = substr_replace($template, '[' . $this->executionStackSize . ']', 0, strpos($template, '{') + 1);
$template = substr_replace($template, '[/' . $this->executionStackSize . ']', strlen($template) - 1);
$parent = $this->executionStackSize;

++$this->executionStackSize;
}
return $template;

return preg_replace_callback(
'/(#[a-z_]+\{(?:[^\{\}]|(?R))+\})/s',
function($template) use($parent) {
return $this->compileTemplate($template, $parent);
},
$template
);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/TextGenerator/TextFunction/SetFunctionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public function testTagOverwritting()
{
$this->textGenerator->compile("#set{@test|Lorem2}@test");
$result = $this->textGenerator->generate(['test' => 'Lorem1']);
$this->assertEquals('Lorem1', $result);
$this->assertEquals('Lorem2', $result);
}

public function testTagOverwritting2()
{
$this->textGenerator->compile("#set{@test|Lorem2}#set{@test|Lorem3}@test");
$result = $this->textGenerator->generate([]);
$this->assertEquals('Lorem2', $result);
$this->assertEquals('Lorem3', $result);
}

public function testAssignementWithFunctionCall()
Expand Down

0 comments on commit 18bb92a

Please sign in to comment.