-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
2,351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
## Example project | ||
|
||
### Modules | ||
Example project contains 2 modules: | ||
- [simple](https://github.com/bipokot/KabuExamples/tree/master/simple) - demonstrates basic pattern syntax capabilities | ||
- [complex](https://github.com/bipokot/KabuExamples/tree/master/complex) - shows how to build more complex DSLs via patterns with extension points | ||
|
||
Each example has a unique permanent code (`Example-XXX`). | ||
|
||
### Pattern samples from actual tests | ||
Take a look at [pattern samples](samples) used to test Kabu itself. | ||
|
||
Each sample consists of three parts: | ||
1. `raw` - pattern itself and encoded parameters of target function placed after `//` separator | ||
2. `sample` - actual runtime Kotlin statement (or expression) which must match designated pattern | ||
3. `termination` - arguments which will be passed to the target function during termination | ||
|
||
For example: | ||
```json | ||
{ | ||
"raw" : "s..b + i // s b i", | ||
"sample" : "\"bbb\"..true + 13", | ||
"termination" : "bbb, true, 13" | ||
} | ||
``` | ||
may be interpreted as following: | ||
```kotlin | ||
@GlobalPattern("s..b + i") | ||
fun targetFunction(s: String, b: Boolean, i: Int) { | ||
print(listOf(s, b, i).joinToString()) | ||
} | ||
|
||
fun main() { | ||
"bbb"..true + 13 // prints: bbb, true, 13 | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
|
||
# Extending a pattern | ||
All patterns (local and global) can be extended. To extend a pattern you need to define one or more *extension points* in the pattern. | ||
|
||
## Extension point | ||
*Extension point* is a `@Extend`-annotated empty lambda placed somewhere in a pattern. It serves 2 purposes: | ||
- it defines a place inside the pattern where the extension will be made | ||
- *"Where we want to extend the pattern"* | ||
- it binds itself with required components via parameters of its `@Extend` annotation | ||
|
||
Those required components are: | ||
- *Context class* | ||
- *"Which operations will extend the pattern?"* | ||
- *Context creator(s)* | ||
- *"How we want to create the context class?"* | ||
|
||
### Context name | ||
The binding between an extension point and context class is done by a *context name*. *Context name* is an identifier-like string used to search for context creators and context classes for particular extension point. | ||
|
||
### Restrictions | ||
The lambda of extension point must be *inferrable*. | ||
In other words the lambda must be placed in such a position that will allow compiler to infer its exact type (generally speaking, the lambda must not appear as a first evaluated argument of expression). | ||
|
||
### `@Extend` annotation parameters | ||
|
||
#### `context` | ||
`context=<CONTEXT_NAME>(<PARAMETERS>)` - where `<CONTEXT_NAME>` is the chosen name for context. `<PARAMETERS>` defines a comma separated list of parameters which will be passed to a chosen context creator. | ||
Each parameter must be one of the [target function](targetFunctions.md) parameter names (including `this` if receiver is used). If detected that one or more of chosen parameters will be unknown by the moment of context creator invocation, you will get a diagnostic message with a list of parameters available as context creator parameters for the extension point. | ||
In that way we can parameterize a context class instantiation with one or more runtime values. `Example-015` demonstrates context parametrization. | ||
|
||
**Context creator dispatching** | ||
|
||
The processor will search for accessible context creators with designated *context name* and will choose the only appropriate context creator with matching parameter types list. | ||
An error occurs if there are no applicable context creators or there are more than one of them. | ||
|
||
#### `parameter` | ||
`parameter=<PARAMETER_NAME>` - where `<PARAMETER_NAME>` defines a parameter of the [target function](targetFunctions.md), which will receive an instance of context class associated with the extension point. In other words, we can access context class data that was created/modified during the lambda invocation. This functionality may be used in builder-style DSLs. | ||
|
||
#### `result` (not yet supported) | ||
`result=<PARAMETER_NAME>` - where `<PARAMETER_NAME>` defines a parameter of the [target function](targetFunctions.md), which will receive a returned value of the lambda invocation. | ||
|
||
## Context class | ||
Context class defines a scope for local [target functions](targetFunctions.md) (`@LocalPattern` functions). Context class must be a non-abstract top level class. A context class may be used for different extension points. | ||
|
||
Member functions of a context class which are annotated with `@LocalPattern` annotation constitute a set of allowed operations inside a lambda. Statements corresponding to patterns of those local [target functions](targetFunctions.md) will be the only accessible way to interact with an instance of the context class. | ||
|
||
## Context creator(s) | ||
Context creator is a function (or constructor) that creates an instance of context class. A context creator may be used for different extension points. One context class may have several context creators with different parameters lists. | ||
A context creator and an extension point associated with it must share the same *context name*. | ||
|
||
### Restrictions | ||
Context creator: | ||
- must be one of the following: | ||
- a top level function returning an instance of corresponding context class | ||
- a constructor of corresponding context class | ||
- must have `public` or `internal` visibility | ||
- must be annotated with `@ContextCreator` annotation | ||
- may have parameters | ||
|
||
## Unbounded and recursive extensions nesting | ||
Local patterns can have their own extension points, so building complex DSLs with arbitrary depth of extensions nesting is possible. | ||
Recursive (and transitively recursive) extensions nesting is possible too because local pattens can have extension points bound to the same context class. | ||
|
||
```kotlin | ||
// Example-008 | ||
|
||
jsonObject { | ||
"first" - jsonObject { | ||
"inner" - 101 | ||
} | ||
"second" - jsonArray { | ||
+"a" | ||
+jsonArray { | ||
+"1" | ||
+jsonObject { | ||
"deepest" - 1 | ||
"object" - 2 | ||
} | ||
} | ||
+"c" | ||
} | ||
} | ||
``` | ||
|
||
## Steps to define an extension point | ||
1. Choose a *context name* (following the rules for identifier name). It may relate to a context class name. | ||
2. Create a *context class* containing functions marked with `@LocalPattern`, which constitute a set of allowed operations inside a lambda. | ||
3. Define at least one way to create the *context class*. Mark it with a `@ContextCreator` annotation and specify chosen *context name*. | ||
4. Mark an empty lambda with `@Extend` annotation inside a pattern. Specify required parameters of the annotation. | ||
|
||
### Example | ||
```kotlin | ||
class Builder @ContextCreator("ctx") constructor() { | ||
|
||
val actions = mutableListOf<() -> Unit>() | ||
|
||
@LocalPattern("+action") | ||
fun addAction(action: () -> Unit) { | ||
actions += action | ||
} | ||
} | ||
|
||
@GlobalPattern("foo @Extend(context = ctx(), parameter = builder) {}") | ||
fun func(builder: Builder) { | ||
builder.actions.forEach { it() } | ||
} | ||
|
||
fun main() { | ||
foo { | ||
+ { println("action 1") } | ||
+ { println("action 2") } | ||
+ { println("action 3") } | ||
} | ||
} | ||
``` | ||
|
||
Here, the pattern `foo @Extend(context = ctx(), parameter = builder) {}` defines an extension point `@Extend(context = ctx(), parameter = builder) {}`: | ||
- `context = ctx()` - means that no-argument function annotated as creator of "ctx"-named context will be searched in order to create an instance of context class (primary constructor of `Builder` class will be used) | ||
- all `@LocalPattern` expressions will be accessible in that lambda, and their termination will be delegated to the newly created instance of `Builder` class | ||
- `parameter = builder` - means that during termination that created instance of `Builder` class will be passed as `builder` parameter of target function `func` (e.g. to handle collected data) | ||
|
||
## Context mediator class | ||
The processor generates a special *context mediator class* with declarations providing support for local patterns originally declared in a context class. | ||
It is an instance of *context mediator class* that acts as a receiver of lambda of an extension point. *Context mediator class* passes all terminations to the instance of context class. | ||
|
||
Among other reasons this helps to hide context implementation details (intermediate variables, mutable lists, etc.) from a DSL user, providing a clean and well-defined interface inside an extension point lambda. | ||
|
||
## More examples | ||
|
||
```kotlin | ||
// Example-007 | ||
|
||
data class Player( | ||
val name: String, | ||
val number: Int | ||
) | ||
|
||
data class Trophy( | ||
val name: String, | ||
val times: Int | ||
) | ||
|
||
data class FootballTeam( | ||
val name: String, | ||
val isChampion: Boolean, | ||
val players: List<Player>, | ||
val trophies: List<Trophy> | ||
) | ||
|
||
class PlayersBuilder @ContextCreator("playersBuilder") constructor() { | ||
val players = mutableListOf<Player>() | ||
|
||
@LocalPattern("name - number") | ||
fun addPlayer(name: String, number: Int) { | ||
players.add(Player(name, number)) | ||
} | ||
} | ||
|
||
class FootballTeamBuilder @ContextCreator("footballTeamBuilder") constructor() { | ||
|
||
val trophies = mutableListOf<Trophy>() | ||
var isChampion = false | ||
val players = mutableListOf<Player>() | ||
|
||
@LocalPattern("champion") | ||
fun champion() { | ||
isChampion = true | ||
} | ||
|
||
@LocalPattern("!number - trophy") | ||
fun addTrophy(trophy: String, number: Int) { | ||
trophies.add(Trophy(trophy, number)) | ||
} | ||
|
||
@LocalPattern("has outstanding players @Extend(context = playersBuilder(), parameter = builder) {}") | ||
fun addPlayers(builder: PlayersBuilder) { | ||
players.addAll(builder.players) | ||
} | ||
} | ||
|
||
@GlobalPattern("football team name @Extend(context = footballTeamBuilder(), parameter = builder) {}") | ||
fun footballTeam(name: String, builder: FootballTeamBuilder): FootballTeam { | ||
return FootballTeam(name, builder.isChampion, builder.players, builder.trophies) | ||
} | ||
|
||
fun main() { | ||
val team = football team "Tigers" { | ||
champion | ||
|
||
!3 - "Super League Champions" | ||
!26 - "National League" | ||
|
||
has outstanding players { | ||
"John Smith" - 10 | ||
"Enrique Hernandez" - 2 | ||
} | ||
} | ||
println(team) | ||
} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
|
||
# Pattern syntax | ||
|
||
Pattern syntax generally corresponds to a Kotlin *statement* syntax (with some exceptions). No control flow structures are allowed (`if`, `while`, `for`, `try-catch`, etc.). | ||
|
||
## Supported operations | ||
Following operations are supported: | ||
- call (invoke): `foo(a, b, c)` | ||
- indexed access (get/set): `foo[a, b, c]` | ||
- access: `foo.bar` | ||
- unary prefix operators | ||
- `-`: `-foo` | ||
- `+`: `+foo` | ||
- `!`: `!foo` | ||
- infix functions: `foo func bar` | ||
- binary operations | ||
- multiplicative | ||
- `*`: `foo * bar` | ||
- `/`: `foo / bar` | ||
- `%`: `foo % bar` | ||
- additive | ||
- `+`: `foo + bar` | ||
- `-`: `foo - bar` | ||
- range | ||
- `..`: `foo .. bar` | ||
- `..<`: `foo ..< bar` (requires opt-in and language version 1.8) | ||
- inclusion checks | ||
- `in`: `foo in bar` | ||
- `!in`: `foo !in bar` | ||
- comparison checks | ||
- `>`: `foo > bar` | ||
- `<`: `foo < bar` | ||
- `>=`: `foo >= bar` | ||
- `<=`: `foo <= bar` | ||
- augmented assignments: | ||
- `+=`: `foo += bar` | ||
- `-=`: `foo -= bar` | ||
- `*=`: `foo *= bar` | ||
- `/=`: `foo /= bar` | ||
- `%=`: `foo %= bar` | ||
- assignments | ||
- regular: `foo = bar` (see `Example-017`) | ||
- indexed: `foo[a, b, c] = bar` | ||
|
||
Not supported operations: | ||
- postfix/prefix increment/decrement operators: `foo++`, `foo--`, `++foo`, `--foo` | ||
- equality checks: `foo == bar`, `foo != bar` | ||
- conjunctions: `foo && bar` | ||
- disjunctions: `foo || bar` | ||
- spread operator: `foo(*bar)` | ||
|
||
## Functional literals | ||
You can use lambdas as building blocks of patterns in all possible ways. Only first statement in a lambdas is taken into account (other statements are ignored). | ||
- `foo { a * b }` | ||
- `a + {{{ b / c }}}` | ||
- `{ foo } / !{ bar }` | ||
|
||
## Extension points | ||
> See [documentation on pattern extending](patternExtension.md) | ||
A `@Extend`-annotated empty lambda serves as an *extension point* of a pattern. A pattern can have multiple extension points. | ||
|
||
## Pattern samples | ||
Explore [a project with a set of examples](exampleProject.md) and take a look at [pattern samples](samples) used to test Kabu itself. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
[ | ||
{ | ||
"raw" : "s.x - i // s i", | ||
"sample" : "\"abc\".x - 2", | ||
"termination" : "abc, 2" | ||
}, | ||
{ | ||
"raw" : "s.x.y - i // s i", | ||
"sample" : "\"abc\".x.y - 2", | ||
"termination" : "abc, 2" | ||
}, | ||
{ | ||
"raw" : "s.x(y).z[r, t.v { b.w }] - i // s i b", | ||
"sample" : "\"abc\".x(y).z[r, t.v { true.w }] - 101", | ||
"termination" : "abc, 101, true" | ||
}, | ||
{ | ||
"raw" : "xX { b }.Xx - awesome // b", | ||
"sample" : "xX { false }.Xx - awesome", | ||
"termination" : "false" | ||
}, | ||
{ | ||
"raw" : "S.T.A.L.K.E.R - i // i", | ||
"sample" : "S.T.A.L.K.E.R - 2007", | ||
"termination" : "2007" | ||
}, | ||
{ | ||
"raw" : "s.x // s", | ||
"sample" : "\"abc\".x", | ||
"termination" : "abc" | ||
}, | ||
{ | ||
"raw" : "a.b.c.d.e.f.g", | ||
"sample" : "a.b.c.d.e.f.g", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "a.b.c", | ||
"sample" : "a.b.c", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "a.b().c", | ||
"sample" : "a.b().c", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "a.b[d].c", | ||
"sample" : "a.b[d].c", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "a(b).c[d].e", | ||
"sample" : "a(b).c[d].e", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "a(b.c.d)[e].f.g", | ||
"sample" : "a(b.c.d)[e].f.g", | ||
"termination" : "" | ||
}, | ||
{ | ||
"raw" : "z { s.x } // s", | ||
"sample" : "z { \"abc\".x }", | ||
"termination" : "abc" | ||
} | ||
] |
Oops, something went wrong.