Skip to content

Commit 5f68aae

Browse files
authored
Merge pull request #403 from fandango-fuzzer/dev
Dev
2 parents 0a6fcc5 + b257244 commit 5f68aae

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

docs/Conversion.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ kernelspec:
1111
---
1212

1313
(sec:conversion)=
14-
# Data Conversions
14+
# Data Converters
1515

1616
When defining a complex input format, some parts may be the result of applying an _operation_ on another, more structured part.
1717
Most importantly, content may be _encoded_, _compressed_, or _converted_.
@@ -275,3 +275,70 @@ Typical applications include:
275275

276276
Even though parts of the input are encoded (or compressed), you can still use _constraints_ to shape them.
277277
And if the encoding or compression can be inverted, you can also use it to _parse_ inputs again.
278+
279+
280+
## Converters vs. Constraints
281+
282+
Since converters (and generally, generators) can do anything, they can be used for any purpose, including producing solutions that normally would come from [constraints](sec:constraints).
283+
284+
As an example, consider the credit card grammar from the [chapter on binary inputs](sec:binary):
285+
286+
```{code-cell}
287+
:tags: ["remove-input"]
288+
# show grammar except '<byte>'
289+
!grep '::=' credit_card.fan; grep 'where' credit_card.fan
290+
```
291+
292+
Instead of having a constraint (`where`) that expresses the relationship between `<number>` and `<check_digit>`, we can easily enhance the grammar with converters between `<number>` and `<credit_card_number>`:
293+
294+
```python
295+
<credit_card_number> ::= <number> <check_digit> := add_check_digit(str(<number>))
296+
<number> ::= <digit>{15} := strip_check_digit(str(<credit_card_number>))
297+
```
298+
299+
with
300+
301+
```python
302+
def add_check_digit(number: str) -> str:
303+
"""Add a check digit to the credit card number `number`."""
304+
check_digit = credit_card_check_digit(number)
305+
return number + check_digit
306+
```
307+
308+
and
309+
310+
```
311+
def strip_check_digit(number: str) -> str:
312+
"""Strip the check digit from the credit card number `number`."""
313+
return number[:-1]
314+
```
315+
316+
The resulting `.fan` spec [`credit_card-gen.fan`](credit_card-gen.fan)
317+
has the same effect as the original [`credit_card.fan`](credit_card.fan) from the [chapter on binary inputs](sec:binary):
318+
319+
```shell
320+
$ fandango fuzz -f credit_card-gen.fan -n 10
321+
```
322+
323+
```{code-cell}
324+
:tags: ["remove-input"]
325+
!fandango fuzz -f credit_card-gen.fan -n 10
326+
```
327+
328+
329+
Now, these two functions `add_check_digit()` and `strip_check_digit()` are definitely longer than our original constraint
330+
331+
```python
332+
where <check_digit> == credit_card_check_digit(str(<number>))
333+
```
334+
335+
However, they are not necessarily more complex.
336+
And they are more efficient, as they provide a solution right away.
337+
So when should one use constraints, and when converters?
338+
339+
:::{tip}
340+
In general:
341+
342+
* If you have a simple, _operational_ way to solve a problem, consider a _converter_.
343+
* If you want a simple, _declarative_ way to specify your needs, use a _constraint_.
344+
:::

docs/credit_card-gen.fan

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<start> ::= <credit_card_number>
2+
<credit_card_number> ::= <number> <check_digit> := add_check_digit(str(<number>))
3+
<number> ::= <digit>{15} := strip_check_digit(str(<credit_card_number>))
4+
<check_digit> ::= <digit>
5+
6+
def add_check_digit(number: str) -> str:
7+
"""Add a check digit to the credit card number `number`."""
8+
check_digit = credit_card_check_digit(number)
9+
return number + check_digit
10+
11+
def strip_check_digit(number: str) -> str:
12+
"""Strip the check digit from the credit card number `number`."""
13+
return number[:-1]
14+
15+
def credit_card_check_digit(number: str) -> str:
16+
"""Create a check digit for the credit card number `number`."""
17+
luhn_lookup = {
18+
"0": 0,
19+
"1": 2,
20+
"2": 4,
21+
"3": 6,
22+
"4": 8,
23+
"5": 1,
24+
"6": 3,
25+
"7": 5,
26+
"8": 7,
27+
"9": 9,
28+
}
29+
30+
# Calculate sum
31+
length = len(number) + 1
32+
reverse = number[::-1]
33+
tot = 0
34+
pos = 0
35+
while pos < length - 1:
36+
tot += luhn_lookup[reverse[pos]]
37+
if pos != (length - 2):
38+
tot += int(reverse[pos + 1])
39+
pos += 2
40+
41+
# Calculate check digit
42+
check_digit = (10 - (tot % 10)) % 10
43+
return str(check_digit)

0 commit comments

Comments
 (0)