Skip to content

Commit 14066c0

Browse files
committed
adding regular expression contracts
1 parent f93ae59 commit 14066c0

File tree

7 files changed

+123
-15
lines changed

7 files changed

+123
-15
lines changed

doc/main/contracts.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ first-order properties (things like `typeof` checks).
100100
| `Void` | Either `null` or `undefined` |
101101

102102

103+
### Regular Expressions
104+
105+
You can test that a value matches a regular expression by using a
106+
regular expression literal:
107+
108+
```js
109+
@ (/username:\s*[a-zA-Z]*$/) -> Bool
110+
function checkUsername(str) {
111+
// ...
112+
return true;
113+
}
114+
115+
checkUsername("username: bob"); // passes
116+
checkUsername("user: bob"); // fails
117+
```
118+
103119
### Custom Predicate Contracts
104120

105121
All of the basic contracts are built with predicates (functions that

macros/disabled.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,11 @@ let import = macro {
465465
});
466466
return c;
467467
}
468-
function self() {
469-
var name = 'self';
468+
function reMatch(re) {
469+
var contractName = re.toString();
470+
return check(function (val) {
471+
return re.test(val);
472+
}, contractName);
470473
}
471474
function and(left, right) {
472475
if (!(left instanceof Contract)) {
@@ -571,13 +574,10 @@ let import = macro {
571574
return null == val;
572575
}, 'Null'),
573576
check: check,
577+
reMatch: reMatch,
574578
fun: fun,
575579
or: or,
576580
and: and,
577-
self: new Contract('self', 'self', function (b) {
578-
return function () {
579-
};
580-
}),
581581
repeat: repeat,
582582
optional: optional,
583583
object: object,
@@ -731,8 +731,25 @@ macro predicate_contract {
731731
}
732732
}
733733

734+
macro regex {
735+
case {_ $tok } => {
736+
var tok = #{$tok};
737+
if (tok[0].token.type === parser.Token.RegularExpression) {
738+
return tok;
739+
}
740+
throwSyntaxCaseError("Not a regular expression");
741+
}
742+
}
743+
744+
macro regex_contract {
745+
rule { $re:regex } => {
746+
_c.reMatch($re)
747+
}
748+
}
749+
734750

735751
macro non_bin_contract {
752+
rule { $contract:regex_contract } => { $contract }
736753
rule { $contract:predicate_contract } => { $contract }
737754
rule { $contract:function_contract } => { $contract }
738755
rule { $contract:object_contract } => { $contract }

macros/index.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,11 @@ let import = macro {
465465
});
466466
return c;
467467
}
468-
function self() {
469-
var name = 'self';
468+
function reMatch(re) {
469+
var contractName = re.toString();
470+
return check(function (val) {
471+
return re.test(val);
472+
}, contractName);
470473
}
471474
function and(left, right) {
472475
if (!(left instanceof Contract)) {
@@ -571,13 +574,10 @@ let import = macro {
571574
return null == val;
572575
}, 'Null'),
573576
check: check,
577+
reMatch: reMatch,
574578
fun: fun,
575579
or: or,
576580
and: and,
577-
self: new Contract('self', 'self', function (b) {
578-
return function () {
579-
};
580-
}),
581581
repeat: repeat,
582582
optional: optional,
583583
object: object,
@@ -731,8 +731,25 @@ macro predicate_contract {
731731
}
732732
}
733733

734+
macro regex {
735+
case {_ $tok } => {
736+
var tok = #{$tok};
737+
if (tok[0].token.type === parser.Token.RegularExpression) {
738+
return tok;
739+
}
740+
throwSyntaxCaseError("Not a regular expression");
741+
}
742+
}
743+
744+
macro regex_contract {
745+
rule { $re:regex } => {
746+
_c.reMatch($re)
747+
}
748+
}
749+
734750

735751
macro non_bin_contract {
752+
rule { $contract:regex_contract } => { $contract }
736753
rule { $contract:predicate_contract } => { $contract }
737754
rule { $contract:function_contract } => { $contract }
738755
rule { $contract:object_contract } => { $contract }

src/contracts.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,8 +583,11 @@
583583
return c;
584584
}
585585

586-
function self() {
587-
var name = "self";
586+
function reMatch(re) {
587+
var contractName = re.toString();
588+
return check(function(val) {
589+
return re.test(val);
590+
}, contractName);
588591
}
589592

590593
function and(left, right) {
@@ -614,6 +617,7 @@
614617
})
615618
}
616619

620+
617621
function or(left, right) {
618622
if (!(left instanceof Contract)) {
619623
if (typeof left === "function") {
@@ -674,10 +678,10 @@
674678
Void: check(function(val) { return null == val; }, "Null"),
675679

676680
check: check,
681+
reMatch: reMatch,
677682
fun: fun,
678683
or: or,
679684
and: and,
680-
self: new Contract("self", "self", function(b) { return function() {}; }),
681685
repeat: repeat,
682686
optional: optional,
683687
object: object,

src/macros-disabled.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,25 @@ macro predicate_contract {
145145
}
146146
}
147147

148+
macro regex {
149+
case {_ $tok } => {
150+
var tok = #{$tok};
151+
if (tok[0].token.type === parser.Token.RegularExpression) {
152+
return tok;
153+
}
154+
throwSyntaxCaseError("Not a regular expression");
155+
}
156+
}
157+
158+
macro regex_contract {
159+
rule { $re:regex } => {
160+
_c.reMatch($re)
161+
}
162+
}
163+
148164

149165
macro non_bin_contract {
166+
rule { $contract:regex_contract } => { $contract }
150167
rule { $contract:predicate_contract } => { $contract }
151168
rule { $contract:function_contract } => { $contract }
152169
rule { $contract:object_contract } => { $contract }

src/macros.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,25 @@ macro predicate_contract {
145145
}
146146
}
147147

148+
macro regex {
149+
case {_ $tok } => {
150+
var tok = #{$tok};
151+
if (tok[0].token.type === parser.Token.RegularExpression) {
152+
return tok;
153+
}
154+
throwSyntaxCaseError("Not a regular expression");
155+
}
156+
}
157+
158+
macro regex_contract {
159+
rule { $re:regex } => {
160+
_c.reMatch($re)
161+
}
162+
}
163+
148164

149165
macro non_bin_contract {
166+
rule { $contract:regex_contract } => { $contract }
150167
rule { $contract:predicate_contract } => { $contract }
151168
rule { $contract:function_contract } => { $contract }
152169
rule { $contract:object_contract } => { $contract }

test/test_contracts.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,4 +904,24 @@ function foo guarded at line: 881
904904
blaming: (calling context for foo)
905905
`
906906
})
907+
908+
it("should work for the regex contract", function() {
909+
@ (/username:\s*[a-zA-Z]*$/) -> Bool
910+
function checkUsername(str) {
911+
return true;
912+
}
913+
914+
checkUsername("username: bob");
915+
blame of {
916+
checkUsername("bad");
917+
} should be `checkUsername: contract violation
918+
expected: /username:\s*[a-zA-Z]*$/
919+
given: 'bad'
920+
in: the 1st argument of
921+
(/username:\s*[a-zA-Z]*$/) -> Bool
922+
function checkUsername guarded at line: 910
923+
blaming: (calling context for checkUsername)
924+
`
925+
926+
})
907927
});

0 commit comments

Comments
 (0)