Skip to content

Commit

Permalink
Use a more consistent syntax for references
Browse files Browse the repository at this point in the history
  • Loading branch information
flbulgarelli committed Feb 4, 2023
1 parent 6ea23e4 commit d19531d
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 70 deletions.
32 changes: 18 additions & 14 deletions docs/edlspec.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,6 @@ expectation "`tell` must be called with value 10 and true":
expectation "`play` must be called with this":
%% equivalent to * Calls:play:WithSelf
calls `play` with self;
expectation "uses a repeat with a non-literal amount of iterations":
%% matches repeat blocks where the repeat count expression is a non-literal
%% and the loop body is anything
uses repeat with (nonliteral, anything);
expectation "uses a repeat with a non-literal amount of iterations":
%% shorter version of previous example
uses repeat with nonliteral;
```

Most of the matchers are designed to perform literal queries, but some of them allow more complex matching:
Expand All @@ -230,6 +221,18 @@ expectation "a method that performs boolean operations must be declared":
expectation "`getAge` must not return a hardcoded value":
%% equivalent to Intransitive:getAge Returns:WithNonliteral
within `getAge` returns with nonliteral;
expectation "uses a repeat with a non-literal amount of iterations":
%% matches repeat blocks where the repeat count expression is a non-literal
%% and the loop body is anything
uses repeat with (nonliteral, anything);
expectation "uses a repeat with a non-literal amount of iterations":
%% shorter version of previous example
uses repeat with nonliteral;
expectation "uses items[0] to get first item":
calls get at with (&`items`, 0);
```

As you can see in previous examples, many of the simplest matchers can also be used in the standard expectation syntax. However, EDL also supports the `that` matcher,
Expand Down Expand Up @@ -333,10 +336,11 @@ This is the complete list of inspections that support matchers:
### Supported matchers

* `(<matcher1>, <matcher2>.., <matcherN>)`: matches a tuple of expressions, like a callable's arguments or a control structure parts. If less elements than required are passed, the list is padded with `anything` matchers.
* `<character>`: matches a single character
* `<number>`: matches a number literal
* `<string>`: matches a single string
* `<symbol>`: matches a symbol literal
* `'<character>'`: matches a single character - e.g. `'h'`
* `<number>`: matches a number literal - e.g. `5` or `10.9`
* `"<string>"`: matches a string literal - e.g. `"hello"`
* `` `<symbol>` ``: matches a symbol literal - e.g. `` `id` ``
* `` &`<identifier>` ``: matches a reference - e.g. `` &`counter` ``
* `anything`: matches anything
* `false` and `true`: matches the `true` and `false` literals
* `literal`: matches any literal
Expand Down Expand Up @@ -428,7 +432,7 @@ expectation "pacakge `vet` must declare a class, enum or interface named `Pet`":
expectation "`Pet` must declare `eat` and `Owner` must send it":
%% however in most cases, it is better to declare two different, separate
%% expectations
(within `Pet` declares `eat`) and (within `Owner` sends `eat`);
(within `Pet` declares `eat`) and (within `Owner` calls `eat`);
```

# ⚠️ Caveats
Expand Down
31 changes: 30 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,13 @@ Finally, you can provide a _matcher_ to many of the available expectations, that
> code.expect 'DeclaresAttribute:WithLiteral'
=> true # because weight is initialized with a literal value
> code.expect 'DeclaresAttribute:WithNil'
=> false # because no attribute is initialied with null
=> false # because no attribute is initialized with null
> code.expect 'DeclaresVariable:aPlace:WithReference:buenosAires'
=> true # because the reference buenosAires is assigned to aPlace
> code.expect 'DeclaresVariable:aBird:WithReference:aPlace'
=> false # because the reference aPlace is NOT assigned to aBird...
> code.expect 'aBird', 'Uses:aPlace'
=> true # ...eventhough it is used
```

The complete list of supported matchers is the following:
Expand All @@ -178,6 +184,29 @@ The complete list of supported matchers is the following:
* `WithNumber:value`
* `WithString:"value"`

## Custom expectations

Finally, Mulang support custom expectations, defined using the [EDL]((./edlspec)) language:

```ruby
> code = Mulang::Code.native "JavaScript", %q{
aBird['name'] = 'Norita';
aBird['energy'] = 100;
aBird.fly(rosario);
}
> code.custom_expect %q{
expectation "sets `aBird`'s name": calls set at with (&`aBird`, "name");
expectation "sets `'Pepita'` as `aBird`'s name": calls set at with (&`aBird`, "name", "Pepita");
expectation "assigns `100` to `aBird`'s energy ": calls set at with (&`aBird`, "energy", 100);
expectation "makes `aBird`'s fly to `rosario` ": calls `fly` with (&`aBird`, &`rosario`);
}
=> {
"sets `aBird`'s name"=>true,
"sets `'Pepita'` as `aBird`'s name"=>false,
"assigns `100` to `aBird`'s energy "=>true,
"makes `aBird`'s fly to `rosario` "=>true
}
```

# Contributors

Expand Down
107 changes: 56 additions & 51 deletions docs/inspections.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,57 +74,62 @@ The power of Mulang is grounded on more than 120 different kind of inspections
> ⚠️ Please notice that the operators inspections are the preferred and most reliable way of checking
> usage of language primitives. For example, prefer `UsesPlus` over `Uses:+`
| Inspection | Meaning
|----------------------------------|-----------------------
| `UsesAbsolute` | is the numeric `abs`-like absolute operator used?
| `UsesAllSatisfy` | is the collection `all`-like / `every`-like operator used?
| `UsesAnd` | is the `&&`-like and operator used?
| `UsesAnySatisfy` | is the collection `any`-like / `some`-like operator used?
| `UsesBackwardComposition` | is the `.`-like functional backward composition operator used?
| `UsesBitwiseAnd` | is the bit-level `&`-like and operator used?
| `UsesBitwiseLeftShift` | is the bit-level left `<<`-like shift operator used?
| `UsesBitwiseOr` | is the bit-level `|`-like or operator used?
| `UsesBitwiseRightShift` | is the bit-level right `>>`-like shift operator used?
| `UsesBitwiseXor` | is the bit-level `^`-like xor operator used?
| `UsesCeil` | is the numeric `ceil`-like ceiling operator used?
| `UsesCollect` | is the collection `map`-like operator used?
| `UsesCount` | is the collection `count`-like operator used?
| `UsesDetect` | is the collection `find`-like search operator used?
| `UsesDetectMax` | is the collection `max`-like maximum operator used?
| `UsesDetectMin` | is the collection `min`-like minumum operator used?
| `UsesDivide` | is the numeric `/` operator used?
| `UsesEqual` | is the `===`-like equal operator used?
| `UsesFlatten` | is the collection `flatten`-like operator used?
| `UsesFloor` | is the numeric `ceil`-like floor operator used?
| `UsesForwardComposition` | is the `>>`-like functional forward composition operator used?
| `UsesGather` | is the collection `flatmap`-like operator used?
| `UsesGetAt` | is the collection `[]`-like operator used?
| `UsesGreaterOrEqualThan` | is the `>=` operator used?
| `UsesGreaterThan` | is the `>` operator used?
| `UsesHash` | is the `hashcode` operator used?
| `UsesInject` | is the collection `reduce`-like / `fold`-like operator used?
| `UsesLessOrEqualThan` | is the `<=` operator used?
| `UsesLessThan` | is the `<` operator used?
| `UsesMax` | is the `max`-like maximum value binary operator used?
| `UsesMin` | is the `min`-like minimum value binary operator used?
| `UsesMinus` | is the numeric `-` operator used?
| `UsesModulo` | is the numeric `%-like` modulo operator used?
| `UsesMultiply` | is the numeric `*` operator used?
| `UsesNegation` | is the `!`-like not operator used?
| `UsesNotEqual` | is the `!==`-like distinct operator used?
| `UsesNotSame` | is the not reference-identical operator used?
| `UsesNotSimilar` | is the not equal-ignoring-type operator used?
| `UsesOr` | is the `||`-like or operator used?
| `UsesOtherwise` | is the guard's otherwise operator used?
| `UsesPlus` | is the numeric `+` operator used?
| `UsesPush` | is the collection `insertAtEnd`-like operator used?
| `UsesRound` | is the numeric `round`-like round operator used?
| `UsesSame` | is the reference-identical operator used?
| `UsesSelect` | is the collection `filter`-like operator used?
| `UsesSetAt` | is the collection `[]=`-like operator used?
| `UsesSimilar` | is the equal-ignoring-type operator used?
| `UsesSize` | is the collection `length`-like size operator used?

Primitive operators inspections are provided in two flavors:

* `Uses`: check whether the operator is referred
* `Calls`: check whether the operator is actually called within a function, procedure or method call. `Calls` inspections support matchers!

| `Uses` Inspection | `Calls` Inspection | Meaning
|----------------------------------|-----------------------------------|-----------------------
| `UsesAbsolute` | `CallsAbsolute` | is the numeric `abs`-like absolute operator used/called?
| `UsesAllSatisfy` | `CallsAllSatisfy` | is the collection `all`-like / `every`-like operator used/called?
| `UsesAnd` | `CallsAnd` | is the `&&`-like and operator used/called?
| `UsesAnySatisfy` | `CallsAnySatisfy` | is the collection `any`-like / `some`-like operator used/called?
| `UsesBackwardComposition` | `CallsBackwardComposition` | is the `.`-like functional backward composition operator used/called?
| `UsesBitwiseAnd` | `CallsBitwiseAnd` | is the bit-level `&`-like and operator used/called?
| `UsesBitwiseLeftShift` | `CallsBitwiseLeftShift` | is the bit-level left `<<`-like shift operator used/called?
| `UsesBitwiseOr` | `CallsBitwiseOr` | is the bit-level `|`-like or operator used/called?
| `UsesBitwiseRightShift` | `CallsBitwiseRightShift` | is the bit-level right `>>`-like shift operator used/called?
| `UsesBitwiseXor` | `CallsBitwiseXor` | is the bit-level `^`-like xor operator used/called?
| `UsesCeil` | `CallsCeil` | is the numeric `ceil`-like ceiling operator used/called?
| `UsesCollect` | `CallsCollect` | is the collection `map`-like operator used/called?
| `UsesCount` | `CallsCount` | is the collection `count`-like operator used/called?
| `UsesDetect` | `CallsDetect` | is the collection `find`-like search operator used/called?
| `UsesDetectMax` | `CallsDetectMax` | is the collection `max`-like maximum operator used/called?
| `UsesDetectMin` | `CallsDetectMin` | is the collection `min`-like minumum operator used/called?
| `UsesDivide` | `CallsDivide` | is the numeric `/` operator used/called?
| `UsesEqual` | `CallsEqual` | is the `===`-like equal operator used/called?
| `UsesFlatten` | `CallsFlatten` | is the collection `flatten`-like operator used/called?
| `UsesFloor` | `CallsFloor` | is the numeric `ceil`-like floor operator used/called?
| `UsesForwardComposition` | `CallsForwardComposition` | is the `>>`-like functional forward composition operator used/called?
| `UsesGather` | `CallsGather` | is the collection `flatmap`-like operator used/called?
| `UsesGetAt` | `CallsGetAt` | is the collection `[]`-like operator used/called?
| `UsesGreaterOrEqualThan` | `CallsGreaterOrEqualThan` | is the `>=` operator used/called?
| `UsesGreaterThan` | `CallsGreaterThan` | is the `>` operator used/called?
| `UsesHash` | `CallsHash` | is the `hashcode` operator used/called?
| `UsesInject` | `CallsInject` | is the collection `reduce`-like / `fold`-like operator used/called?
| `UsesLessOrEqualThan` | `CallsLessOrEqualThan` | is the `<=` operator used/called?
| `UsesLessThan` | `CallsLessThan` | is the `<` operator used/called?
| `UsesMax` | `CallsMax` | is the `max`-like maximum value binary operator used/called?
| `UsesMin` | `CallsMin` | is the `min`-like minimum value binary operator used/called?
| `UsesMinus` | `CallsMinus` | is the numeric `-` operator used/called?
| `UsesModulo` | `CallsModulo` | is the numeric `%-like` modulo operator used/called?
| `UsesMultiply` | `CallsMultiply` | is the numeric `*` operator used/called?
| `UsesNegation` | `CallsNegation` | is the `!`-like not operator used/called?
| `UsesNotEqual` | `CallsNotEqual` | is the `!==`-like distinct operator used/called?
| `UsesNotSame` | `CallsNotSame` | is the not reference-identical operator used/called?
| `UsesNotSimilar` | `CallsNotSimilar` | is the not equal-ignoring-type operator used/called?
| `UsesOr` | `CallsOr` | is the `||`-like or operator used/called?
| `UsesOtherwise` | `CallsOtherwise` | is the guard's otherwise operator used/called?
| `UsesPlus` | `CallsPlus` | is the numeric `+` operator used/called?
| `UsesPush` | `CallsPush` | is the collection `insertAtEnd`-like operator used/called?
| `UsesRound` | `CallsRound` | is the numeric `round`-like round operator used/called?
| `UsesSame` | `CallsSame` | is the reference-identical operator used/called?
| `UsesSelect` | `CallsSelect` | is the collection `filter`-like operator used/called?
| `UsesSetAt` | `CallsSetAt` | is the collection `[]=`-like operator used/called?
| `UsesSimilar` | `CallsSimilar` | is the equal-ignoring-type operator used/called?
| `UsesSize` | `CallsSize` | is the collection `length`-like size operator used/called?
| `UsesSlice` | `CallsSlice` | is the slicing operator - like Ruby's `[..]` or Python's `[:]` - used/called?

## Imperative Inspections

Expand Down
6 changes: 5 additions & 1 deletion spec/EdlSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ spec = do
test "within `bar` calls `foo` with (0, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsNumber 0, IsSelf]))
test "within `bar` calls `foo` with (\"hello\", self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsString "hello", IsSelf]))
test "within `bar` calls `foo` with (`hello`, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsSymbol "hello", IsSelf]))
test "within `bar` calls `foo` with (&hello, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsReference "hello", IsSelf]))
test "within `bar` calls `foo` with (&`hello`, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsReference "hello", IsSelf]))
test "within `bar` calls `foo` with ('a', self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsChar 'a', IsSelf]))
test "within `bar` calls `foo` with (true, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsTrue, IsSelf]))
test "within `bar` calls `foo` with (false, self)" (simpleMatchingWithin "bar" "calls" (Named "foo") (Matching [IsFalse, IsSelf]))
Expand All @@ -155,6 +155,10 @@ spec = do
test "declares function like `total` that (uses logic)" (simpleMatching "declares function" (Like "total") (Matching [That (simple "uses logic" Any)]))
test "declares function like `total` that (uses math)" (simpleMatching "declares function" (Like "total") (Matching [That (simple "uses math" Any)]))

test "calls size with (&`items`)" (simpleMatching "calls size" Any (Matching [IsReference "items"]))
test "calls get at with (&`items`, 0)" (simpleMatching "calls get at" Any (Matching [IsReference "items", IsNumber 0.0]))
test "calls set at with (&`items`, 0, &`first_item`)" (simpleMatching "calls set at" Any (Matching [IsReference "items", IsNumber 0.0, IsReference "first_item"]))

test "declares function like `total` that (returns that (uses math))" (
simpleMatching "declares function" (Like "total") (Matching [That (
simpleMatching "returns" Any (Matching [That (
Expand Down
8 changes: 5 additions & 3 deletions src/Language/Mulang/Edl/Lexer.x
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ tokens :-
' @char ' { mkChar }
\" @string \" { mkString TString }
` @symbol ` { mkString TSymbol }
& @identifier { mkReference }
&` @symbol ` { mkReference }
("+" | "-")? @float_number { token TNumber readFloat }
("+" | "-")? $digit+ { token TNumber (fromIntegral.readInt) }

Expand Down Expand Up @@ -215,11 +215,13 @@ mkChar :: Action
mkChar = token TChar (head.tail)

mkString :: (String -> Token) -> Action
mkString kind = token kind (\str -> drop 1 . take (length str - 1) $ str)
mkString kind = token kind (stripString 1)

mkIdentifier :: Action
mkIdentifier = token TIdentifier id

mkReference :: Action
mkReference = token TReference (drop 1)
mkReference = token TReference (stripString 2)

stripString n str = drop n . take (length str - 1) $ str
}

0 comments on commit d19531d

Please sign in to comment.