Skip to content

Commit 70862ca

Browse files
committed
add test, fix parsing nested conditions
1 parent 1e09182 commit 70862ca

File tree

6 files changed

+273
-81
lines changed

6 files changed

+273
-81
lines changed

coverage.html

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

src/Parsem/Parser.php

Lines changed: 49 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -128,48 +128,64 @@ public static function parseString(mixed $string, array $arguments = [], bool $s
128128
*/
129129
public static function parseConditions(string $string, array $arguments = [], int $offset = 0, ?string $pattern = null): string
130130
{
131-
preg_match($pattern ?? static::CONDITION_PATTERN, $string, $matches, PREG_OFFSET_CAPTURE, $offset);
132-
if (!$matches) {
133-
return $string;
134-
}
131+
$pattern = $pattern ?? static::CONDITION_PATTERN;
135132

133+
while (preg_match($pattern, $string, $matches, PREG_OFFSET_CAPTURE, $offset)) {
134+
$result = static::getConditionResult($matches, $arguments);
136135

137-
$result = static::getConditionResult($matches, $arguments);
136+
$conditionStart = (int)$matches[0][1];
137+
$conditionLength = strlen($matches[0][0]);
138+
$insideBlockStart = $conditionStart + $conditionLength;
138139

139-
$conditionStart = (int)$matches[0][1];
140-
$conditionLength = strlen($matches[0][0]);
141-
$insideBlockStart = $conditionStart + $conditionLength;
140+
$hasElse = false;
141+
$elseStart = null;
142+
$conditionEnd = null;
142143

143-
$hasElse = false;
144-
$elseCount = preg_match('/<%\s?else\s?%>\n?/', $string, $elseMatches, PREG_OFFSET_CAPTURE, $offset);
145-
if ($elseCount !== false && $elseCount === 1 && $elseMatches) {
146-
$hasElse = true;
147-
$elseStart = (int) $elseMatches[0][1];
148-
$elseTagLength = strlen($elseMatches[0][0]);
149-
$elseBlock = static::parseElseTag($string, $elseStart, $elseTagLength, $arguments, $offset);
150-
} else if ($elseCount > 1) {
151-
throw new RuntimeException("Too many <% else %> tags.");
152-
} else {
153-
$string = static::parseNestedIfs($string, $arguments, $offset + $conditionLength, $insideBlockStart);
154-
}
144+
$nestedOffset = $insideBlockStart;
145+
$nestedIfCount = 0;
146+
$elseMatch = null;
155147

156-
preg_match('/<%\s?endif\s?%>\n?/', $string, $endMatches, PREG_OFFSET_CAPTURE, $offset);
157-
if (!$endMatches) {
158-
throw new RuntimeException("Missing <% endif %> tag.");
159-
}
148+
while (preg_match('/<%\s?(if|else|endif)\s?.*?%>\n?/m', $string, $tagMatches, PREG_OFFSET_CAPTURE, $nestedOffset)) {
149+
$tag = $tagMatches[1][0];
150+
$tagStart = (int)$tagMatches[0][1];
151+
152+
if ($tag === 'if') {
153+
$nestedIfCount++;
154+
} elseif ($tag === 'endif') {
155+
if ($nestedIfCount === 0) {
156+
$conditionEnd = $tagStart;
157+
break;
158+
}
159+
$nestedIfCount--;
160+
} elseif ($tag === 'else' && $nestedIfCount === 0) {
161+
$hasElse = true;
162+
$elseStart = $tagStart;
163+
$elseMatch = $tagMatches[0][0];
164+
}
160165

161-
$conditionEnd = $endMatches[0][1];
162-
$replaceLength = $conditionEnd - $conditionStart + strlen($endMatches[0][0]);
166+
$nestedOffset = $tagStart + strlen($tagMatches[0][0]);
167+
}
163168

164-
if ($hasElse) {
165-
$insideBlock = substr($string, $insideBlockStart, $elseStart - $insideBlockStart);
166-
$string = substr_replace($string, $result ? $insideBlock : $elseBlock, $conditionStart, $replaceLength);
167-
} else {
168-
$insideBlock = substr($string, $insideBlockStart, $conditionEnd - $insideBlockStart);
169-
$string = substr_replace($string, $result ? $insideBlock : '', $conditionStart, $replaceLength);
169+
if ($conditionEnd === null) {
170+
throw new RuntimeException("Missing <% endif %> tag.");
171+
}
172+
173+
$replaceLength = $conditionEnd - $conditionStart + strlen($tagMatches[0][0]);
174+
175+
if ($hasElse) {
176+
$elseTagLength = strlen($elseMatch); // Correctly calculate the length of the `<% else %>` tag
177+
$insideBlock = substr($string, $insideBlockStart, $elseStart - $insideBlockStart);
178+
$elseBlock = substr($string, $elseStart + $elseTagLength, $conditionEnd - ($elseStart + $elseTagLength));
179+
$string = substr_replace($string, $result ? $insideBlock : $elseBlock, $conditionStart, $replaceLength);
180+
} else {
181+
$insideBlock = substr($string, $insideBlockStart, $conditionEnd - $insideBlockStart);
182+
$string = substr_replace($string, $result ? $insideBlock : '', $conditionStart, $replaceLength);
183+
}
184+
185+
$offset = $conditionStart;
170186
}
171187

172-
return static::parseConditions($string, $arguments, $conditionStart, $pattern);
188+
return $string;
173189
}
174190

175191
public static function removeComments(string $string, ?string $pattern = null): string
@@ -541,50 +557,6 @@ protected static function getResultByOperator(mixed $left, ?string $operator, mi
541557
}
542558
}
543559

544-
/**
545-
* Parse the else block of the condition.
546-
* @param string &$string The string to parse (by reference - will be modified in place)
547-
* @param int $elseStart The position of the else tag in the string
548-
* @param int $elseTagLength The length of the else tag
549-
* @param array $arguments Array with the arguments (variables) from the template
550-
* @param int $offset Offset to start replacing the condition from
551-
* @return string The parsed else block
552-
*/
553-
protected static function parseElseTag(string &$string, int $elseStart, int $elseTagLength, array $arguments = [], $offset = 0): string
554-
{
555-
$string = static::parseNestedIfs($string, $arguments, $elseStart + $elseTagLength, (int)$elseStart + $elseTagLength);
556-
557-
preg_match('/<%\s?endif\s?%>\n?/', $string, $endMatches, PREG_OFFSET_CAPTURE, $offset);
558-
if (!$endMatches) {
559-
throw new RuntimeException("Missing <% endif %> tag.");
560-
}
561-
562-
$conditionEnd = $endMatches[0][1];
563-
564-
$elseBlock = substr($string, $elseStart + $elseTagLength, $conditionEnd - $elseStart - $elseTagLength);
565-
566-
return $elseBlock;
567-
}
568-
569-
/**
570-
* Parse nested if conditions.
571-
* @param string $string The string to parse
572-
* @param array $arguments Array with the arguments (variables) from the template
573-
* @param int $searchOffset Offset to start searching for the condition from
574-
* @param int $replaceOffset Offset to start replacing the condition from
575-
* @param string $pattern Pattern to use for the condition
576-
* @return string The parsed string
577-
*/
578-
protected static function parseNestedIfs(string $string, array $arguments = [], int $searchOffset = 0, int $replaceOffset = 0, ?string $pattern = null): string
579-
{
580-
$nestedIfs = preg_match_all(static::CONDITION_PATTERN, $string, $nestedMatches, PREG_OFFSET_CAPTURE, $searchOffset);
581-
if ($nestedIfs !== false && $nestedIfs > 0) {
582-
$string = static::parseConditions($string, $arguments, $replaceOffset, $pattern);
583-
}
584-
585-
return $string;
586-
}
587-
588560
/**
589561
* @param string $defaultMatch The default value match
590562
* @return mixed The default value

tests/Parsem/Parser.complex.phpt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/**
4+
* TEST: Test real-life complex example
5+
* @testCase
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
use Matronator\Parsem\Parser;
11+
use Tester\Assert;
12+
use Tester\TestCase;
13+
14+
require __DIR__ . '/../bootstrap.php';
15+
16+
class ParserComplexTest extends TestCase
17+
{
18+
public function testComplexParsing()
19+
{
20+
$input = file_get_contents(__DIR__ . '/token.input.clar.mtr');
21+
22+
$expected1 = file_get_contents(__DIR__ . '/token.expected.1.clar');
23+
$expected2 = file_get_contents(__DIR__ . '/token.expected.2.clar') . "\n\n";
24+
25+
$args1 = [
26+
"name" => "asdads",
27+
"editableUri" => true,
28+
"userWallet" => "SP39DTEJFPPWA3295HEE5NXYGMM7GJ8MA0TQX379",
29+
"tokenName" => "asdads",
30+
"tokenSymbol" => "ASD",
31+
"tokenSupply" => 8,
32+
"tokenDecimals" => 3,
33+
"tokenURI" => "",
34+
"mintable" => false,
35+
"burnable" => false,
36+
"initialAmount" => 0,
37+
"allowMintToAll" => false,
38+
];
39+
40+
$args2 = [
41+
"name" => "asdads",
42+
"editableUri" => true,
43+
"userWallet" => "SP39DTEJFPPWA3295HEE5NXYGMM7GJ8MA0TQX379",
44+
"tokenName" => "asdads",
45+
"tokenSymbol" => "ASD",
46+
"tokenSupply" => 0,
47+
"tokenDecimals" => 3,
48+
"tokenURI" => "",
49+
"mintable" => true,
50+
"burnable" => false,
51+
"initialAmount" => 0,
52+
"allowMintToAll" => false,
53+
"mintFixedAmount" => false,
54+
"mintAmount" => 0,
55+
];
56+
57+
$parsed1 = Parser::parseString($input, $args1);
58+
$parsed2 = Parser::parseString($input, $args2);
59+
60+
Assert::equal($expected1, $parsed1, 'Parses correctly.');
61+
Assert::equal($expected2, $parsed2, 'Parses correctly.');
62+
}
63+
}
64+
65+
(new ParserComplexTest())->run();

tests/Parsem/token.expected.1.clar

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
3+
;; Implement the `ft-trait` trait defined in the `ft-trait` contract
4+
5+
(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
6+
7+
(define-fungible-token asdads u8)
8+
9+
(define-constant ERR_ADMIN_ONLY (err u401))
10+
(define-constant ERR_NOT_TOKEN_OWNER (err u403))
11+
12+
(define-constant CONTRACT_OWNER 'SP39DTEJFPPWA3295HEE5NXYGMM7GJ8MA0TQX379)
13+
(define-constant TOKEN_URI u"") ;; utf-8 string with token metadata host
14+
(define-constant TOKEN_NAME "asdads")
15+
(define-constant TOKEN_SYMBOL "ASD")
16+
(define-constant TOKEN_DECIMALS u3) ;; 6 units displayed past decimal, e.g. 1.000_000 = 1 token
17+
18+
;; Transfers tokens to a recipient
19+
(define-public (transfer
20+
(amount uint)
21+
(sender principal)
22+
(recipient principal)
23+
(memo (optional (buff 34)))
24+
)
25+
(if (is-eq tx-sender sender)
26+
(begin
27+
(try! (ft-transfer? asdads amount sender recipient))
28+
(print memo)
29+
(ok true)
30+
)
31+
(err ERR_NOT_TOKEN_OWNER)))
32+
33+
(define-public (set-token-uri (token_uri (option buff 34)))
34+
(if (is-eq tx-sender (ft-get-owner asdads))
35+
(begin
36+
(ft-set-token-uri asdads token_uri)
37+
(ok true))
38+
(err ERR_ADMIN_ONLY)))
39+
40+
41+
42+
43+
44+
(ft-mint? asdads u8 CONTRACT_OWNER)

tests/Parsem/token.expected.2.clar

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
3+
;; Implement the `ft-trait` trait defined in the `ft-trait` contract
4+
5+
(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
6+
7+
(define-fungible-token asdads)
8+
9+
(define-constant ERR_ADMIN_ONLY (err u401))
10+
(define-constant ERR_NOT_TOKEN_OWNER (err u403))
11+
12+
(define-constant CONTRACT_OWNER 'SP39DTEJFPPWA3295HEE5NXYGMM7GJ8MA0TQX379)
13+
(define-constant TOKEN_URI u"") ;; utf-8 string with token metadata host
14+
(define-constant TOKEN_NAME "asdads")
15+
(define-constant TOKEN_SYMBOL "ASD")
16+
(define-constant TOKEN_DECIMALS u3) ;; 6 units displayed past decimal, e.g. 1.000_000 = 1 token
17+
18+
;; Transfers tokens to a recipient
19+
(define-public (transfer
20+
(amount uint)
21+
(sender principal)
22+
(recipient principal)
23+
(memo (optional (buff 34)))
24+
)
25+
(if (is-eq tx-sender sender)
26+
(begin
27+
(try! (ft-transfer? asdads amount sender recipient))
28+
(print memo)
29+
(ok true)
30+
)
31+
(err ERR_NOT_TOKEN_OWNER)))
32+
33+
(define-public (set-token-uri (token_uri (option buff 34)))
34+
(if (is-eq tx-sender (ft-get-owner asdads))
35+
(begin
36+
(ft-set-token-uri asdads token_uri)
37+
(ok true))
38+
(err ERR_ADMIN_ONLY)))
39+
40+
41+
42+
(define-public (mint (amount uint) (recipient principal))
43+
(begin
44+
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_ADMIN_ONLY)
45+
(ft-mint? asdads amount recipient)
46+
)
47+
)

tests/Parsem/token.input.clar.mtr

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<# The template for generating the contract.
2+
Uses MTRGen and Pars'Em under the hood #>
3+
4+
;; Implement the `ft-trait` trait defined in the `ft-trait` contract
5+
<# (impl-trait .ft-trait.sip-010-trait) #>
6+
(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
7+
8+
<% if $tokenSupply > 0 %>
9+
(define-fungible-token <% name|kebabCase %> u<% tokenSupply %>)
10+
<% else %>
11+
(define-fungible-token <% name|kebabCase %>)
12+
<% endif %>
13+
14+
(define-constant ERR_ADMIN_ONLY (err u401))
15+
(define-constant ERR_NOT_TOKEN_OWNER (err u403))
16+
17+
(define-constant CONTRACT_OWNER '<% userWallet %>)
18+
(define-constant TOKEN_URI u"<% tokenUri %>") ;; utf-8 string with token metadata host
19+
(define-constant TOKEN_NAME "<% name %>")
20+
(define-constant TOKEN_SYMBOL "<% tokenSymbol %>")
21+
(define-constant TOKEN_DECIMALS u<% tokenDecimals %>) ;; 6 units displayed past decimal, e.g. 1.000_000 = 1 token
22+
23+
;; Transfers tokens to a recipient
24+
(define-public (transfer
25+
(amount uint)
26+
(sender principal)
27+
(recipient principal)
28+
(memo (optional (buff 34)))
29+
)
30+
(if (is-eq tx-sender sender)
31+
(begin
32+
(try! (ft-transfer? <% name|kebabCase %> amount sender recipient))
33+
(print memo)
34+
(ok true)
35+
)
36+
(err ERR_NOT_TOKEN_OWNER)))
37+
38+
(define-public (set-token-uri (token_uri (option buff 34)))
39+
(if (is-eq tx-sender (ft-get-owner <% name|kebabCase %>))
40+
(begin
41+
(ft-set-token-uri <% name|kebabCase %> token_uri)
42+
(ok true))
43+
(err ERR_ADMIN_ONLY)))
44+
45+
<# add condition based on if the mint amount is fixed or not #>
46+
<# if it is, the mint function won't take amount as an argument #>
47+
<% if $mintable === true %>
48+
(define-public (mint (amount uint) (recipient principal))
49+
(begin
50+
<% if !$allowMintToAll %>
51+
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_ADMIN_ONLY)
52+
<% endif %>
53+
(ft-mint? <% name|kebabCase %> amount recipient)
54+
)
55+
)
56+
<% endif %>
57+
58+
<% if $initialAmount > 0 %>
59+
(ft-mint? <% name|kebabCase %> u<% initialAmount %> CONTRACT_OWNER)
60+
<% endif %>
61+
62+
<% if !$mintable %>
63+
(ft-mint? <% name|kebabCase %> u<% tokenSupply %> CONTRACT_OWNER)
64+
<% endif %>

0 commit comments

Comments
 (0)