From 6c06491d0bbce0681224ab2cc4ba09ead5a79a60 Mon Sep 17 00:00:00 2001 From: bipokot <10675204+bipokot@users.noreply.github.com> Date: Sat, 24 Jun 2023 21:42:03 +0600 Subject: [PATCH] added docs --- README.md | 572 ++++++++++++++++++++++ doc/exampleProject.md | 37 ++ doc/patternExtension.md | 201 ++++++++ doc/patternSyntax.md | 65 +++ doc/samples/access/data.json | 67 +++ doc/samples/assign/data.json | 152 ++++++ doc/samples/binary/data.json | 122 +++++ doc/samples/call/data.json | 47 ++ doc/samples/captures/data.json | 187 +++++++ doc/samples/complexUserTypes/data.json | 7 + doc/samples/conflicts/data.json | 62 +++ doc/samples/indexing/data.json | 22 + doc/samples/lambda/data.json | 52 ++ doc/samples/misc/data.json | 197 ++++++++ doc/samples/modAssign/data.json | 92 ++++ doc/samples/nullability/data.json | 22 + doc/samples/standardTermination/data.json | 102 ++++ doc/samples/syntheticVars/data.json | 152 ++++++ doc/samples/unary/data.json | 52 ++ doc/targetFunctions.md | 122 +++++ doc/unsafe.md | 19 + 21 files changed, 2351 insertions(+) create mode 100644 README.md create mode 100644 doc/exampleProject.md create mode 100644 doc/patternExtension.md create mode 100644 doc/patternSyntax.md create mode 100644 doc/samples/access/data.json create mode 100644 doc/samples/assign/data.json create mode 100644 doc/samples/binary/data.json create mode 100644 doc/samples/call/data.json create mode 100644 doc/samples/captures/data.json create mode 100644 doc/samples/complexUserTypes/data.json create mode 100644 doc/samples/conflicts/data.json create mode 100644 doc/samples/indexing/data.json create mode 100644 doc/samples/lambda/data.json create mode 100644 doc/samples/misc/data.json create mode 100644 doc/samples/modAssign/data.json create mode 100644 doc/samples/nullability/data.json create mode 100644 doc/samples/standardTermination/data.json create mode 100644 doc/samples/syntheticVars/data.json create mode 100644 doc/samples/unary/data.json create mode 100644 doc/targetFunctions.md create mode 100644 doc/unsafe.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..acee1a2 --- /dev/null +++ b/README.md @@ -0,0 +1,572 @@ + +# Kabu + +## Overview +**Kabu** is an annotation processor for Kotlin which embraces a declarative way of DSLs creation. The processor allows splitting visual representation of DSL from its functionality. + +```kotlin +// Example-001 + +@GlobalPattern("The.Declarative[!way] /= to * create { +{ a > DSL } } - message") +fun motto(message: String) = println(message) + +fun main() { + The.Declarative[!way] /= to * create { +{ a > DSL } } - "👍" + // prints "👍" +} +``` + +Annotate a function with `@GlobalPattern` annotation and set a desired pattern for that function. The processor will generate code so that any expression matching the pattern will call the function with actual arguments used in that expression. + +Think of a pattern as a *visual decoration* of arguments which will be passed to annotated function. +Patterns can be almost as complex as you can do it with Kotlin. + +## Examples +Explore [a project with a set of examples](doc/exampleProject.md). Each documentation example with `Example-XXX` code can be found in that project. + +Feel free to experiment with patterns and have fun! + +### Some of the examples +
+Hello, World! + +```kotlin +// Example-000 + +@GlobalPattern("hello") +fun helloWorld() { + println("Hello, World!") +} + +fun main() { + hello + + /* Prints: + Hello, World! + */ +} +``` + +
+ +
+Book + +```kotlin +// Example-014 + +@GlobalPattern("print book name[author / year] .. description") +fun printBook(name: String, description: String, year: Int, author: String) { + println("'$name' by $author ($year)\n'$description'") +} + +fun main() { + print book "About Nothing"["Smart Person" / 2011].."The best book in the world" + + /* Prints: + 'About Nothing' by Smart Person (2011) + 'The best book in the world' + */ +} +``` + +
+ +
+Repeat string + +```kotlin +// Example-002 + +@GlobalPattern("string * count") +fun repeatString(count: Int, string: String) = buildString { + repeat(count) { append(string) } +} + +fun main() { + println("abc" * 3) + + /* Prints: + abcabcabc + */ +} +``` + +
+ +
+Functional parameters + +```kotlin +// Example-006 + +@GlobalPattern("block onlyIf condition") +fun onlyIf(block: () -> String, condition: Boolean) = if (condition) block() else null + +fun main() { + println({ println("evaluating 'abc'"); "abc" } onlyIf true) + println({ println("evaluating 'def'"); "def" } onlyIf false) + + /* Prints: + evaluating 'abc' + abc + null + */ +} +``` + +
+ +
+Transactions (actual used operator feature) + +```kotlin +// Example-005 + +data class User( + var balance: Int +) + +@GlobalPattern("send[amount] { user1 > user2 }") +fun transaction(amount: Int, user1: User, rank: RankingComparisonInfo, user2: User) { + + fun moveMoney(amount: Int, from: User, to: User) { + // dumb transaction implementation, don't try this at work + from.balance -= amount + to.balance += amount + } + + when (rank) { + GREATER -> moveMoney(amount, from = user1, to = user2) + LESS -> moveMoney(amount, from = user2, to = user1) + } +} + + +fun main() { + val alice = User(1000) + val bob = User(800) + + println("Alice: $alice") + println("Bob: $bob") + + send[200] { alice > bob } + send[100] { alice < bob } + + println("Alice: $alice") + println("Bob: $bob") + + /* Prints: + Alice: User(balance=1000) + Bob: User(balance=800) + Alice: User(balance=900) + Bob: User(balance=900) + */ +} +``` + +
+ +
+Football team (pattern extension) + +```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, + val trophies: List +) + +class PlayersBuilder @ContextCreator("playersBuilder") constructor() { + val players = mutableListOf() + + @LocalPattern("name - number") + fun addPlayer(name: String, number: Int) { + players.add(Player(name, number)) + } +} + +class FootballTeamBuilder @ContextCreator("footballTeamBuilder") constructor() { + + val trophies = mutableListOf() + var isChampion = false + val players = mutableListOf() + + @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) + + /* Prints: + FootballTeam(name=Tigers, isChampion=true, players=[Player(name=John Smith, number=10), Player(name=Enrique Hernandez, number=2)], trophies=[Trophy(name=Super League Champions, times=3), Trophy(name=National League, times=26)]) + */ +} +``` + +
+ +
+Alternative if-else (pattern extension) + +```kotlin +// Example-009 + +class Actions @ContextCreator("actions") constructor() { + val trueActions = mutableListOf<() -> Unit>() + val falseActions = mutableListOf<() -> Unit>() + + @LocalPattern("+action") + fun addTrueAction(action: () -> Unit) { + trueActions += action + } + + @LocalPattern("-action") + fun addFalseAction(action: () -> Unit) { + falseActions += action + } +} + +@GlobalPattern("condition @Extend(context = actions(), parameter = actions) {}") +fun ifElse(condition: Boolean, actions: Actions) { + val actionsToExecute = if (condition) actions.trueActions else actions.falseActions + actionsToExecute.forEach { it() } +} + +fun main() { + val isOperationSuccessful = false + isOperationSuccessful { + + { println("show message 'Success'") } + - { println("show message 'Failure'") } + + + { println("activate button 'Close'") } + - { println("activate button 'Repeat'") } + } + + /* Prints: + show message 'Failure' + activate button 'Repeat' + */ +} +``` + +
+ +
+Alphabet + +```kotlin +// Example-019 + +@GlobalPattern("a{{ b ..< c } !in -d[e, +-{f}[g][{{{}..{h.i = j}}}], k(l){ m += n} + !{o * -p(q {r[s.t.u] = v w x})}]} / y + z") +fun alphabet( + b: Int, + inclusion: InclusionInfo, + e: String, + g: Int, + j: String, + m: Int, + r: Int, + s: Int, + v: String, + z: Int, +) { + listOf(b, inclusion, e, g, j, m, r, s, v, z).forEach(::println) +} + +fun main() { + a{{ 2 ..< c } !in -d["3", +-{f}[5][{{{}..{h.i = "7"}}}], k(l){ 11 += n } + !{o * -p(q {13[17.t.u] = "19" w x})}]} / y + 23 + + /* Prints: + 2 + NOT_IN + 3 + 5 + 7 + 11 + 13 + 17 + 19 + 23 + */ +} +``` + +
+ +
+Gibberish + +```kotlin +// Example-018 + +@GlobalPattern("ᘤ [ᘎ, a, +ᒣ {!b{ᐳƧ}*c}] - ϾϿ(-d[Ⲷ]){ e Ⴖ Ϟ % ᘃ(ᗏ) ᗊ -ᓬ[f] }") +fun gibberish(a: String, b: String, c: String, d: String, e: String, f: String) { + println(a + b + c + d + e + f) +} + +fun main() { + ᘤ [ᘎ, "K", +ᒣ {!"o"{ᐳƧ}*"t"}] - ϾϿ(-"l"[Ⲷ]){ "i" Ⴖ Ϟ % ᘃ(ᗏ) ᗊ -ᓬ["n"] } + + /* Prints: + Kotlin + */ +} +``` + +
+ +## Documentation + +### Terminology +- `pattern` - a string which defines how an expression must look like. [Pattern syntax](doc/patternSyntax.md) generally corresponds to a Kotlin *statement* syntax. +- `target function` - function annotated with one of the pattern annotations (`@GlobalPattern`/`@LocalPattern`), which is to be called when expression matching to the pattern is evaluated + - `global target function` - a target function annotated with `@GlobalPattern` annotation + - `local target function` - a target function annotated with `@LocalPattern` annotation +- `termination` - gathering all required arguments and calling a target function +- `inferrable lambda` - a lambda which exact type can be inferred by a compiler. Usually this means that lambda must not be a first evaluated argument of an operation. + +### Details +- [Pattern syntax](doc/patternSyntax.md) covers features of patterns of `@GlobalPattern` and `@LocalPattern` annotations +- [Target functions](doc/targetFunctions.md) covers supported features of target functions (scope, modifiers, parameters, etc.) +- [Pattern extension](doc/patternExtension.md) describes how to make extension points in your patterns +- [Unsafe features](doc/unsafe.md) explains why they called "unsafe" + +## Features + +### Pattern extensibility +> See [documentation on pattern extending](doc/patternExtension.md) + +Plain simple pattern acts as a comprehensive template, which defines more or less *fixed* structure for future runtime expressions. [Pattern syntax](doc/patternSyntax.md) supports *extension points* in order to express more complex DSLs. + +An *extension point* defines a lambda based scope in which multiple operations from some limited set of allowed operations can be included. These allowed operations are defined by annotating functions of *context class* with `@LocalPattern` annotation. + +```kotlin +// Example-009 + +// context class +class Actions @ContextCreator("actions") constructor() { + val trueActions = mutableListOf<() -> Unit>() + val falseActions = mutableListOf<() -> Unit>() + + @LocalPattern("+action") + fun addTrueAction(action: () -> Unit) { + trueActions += action + } + + @LocalPattern("-action") + fun addFalseAction(action: () -> Unit) { + falseActions += action + } +} + +@GlobalPattern("condition @Extend(context = actions(), parameter = actions) {}") +fun ifElse(condition: Boolean, actions: Actions) { + val actionsToExecute = if (condition) { + actions.trueActions + } else { + actions.falseActions + } + actionsToExecute.forEach { it() } +} + +fun main() { + val isOperationSuccessful = false + isOperationSuccessful { + + { println("show message 'Success'") } + - { println("show message 'Failure'") } + + + { println("activate button 'Close'") } + - { println("activate button 'Repeat'") } + } +} +``` + +### Retrieving actual used operator (comparison/inclusion) +>This feature is *unsafe*. See [unsafe features](doc/unsafe.md). + +Required conditions: +- check (comparison/inclusion) must be in an *inferrable lambda* (at least transitively) of a pattern +- order of [target function](doc/targetFunctions.md) parameters must match the order in which these parameters appear in a pattern +- parameter of type `OperatorInfo` must be included *between* corresponding parameters + +You can use comparison/inclusion operators in any combination and get as many `OperatorInfo` parameters as you wish. + +#### Comparison check +For example `transaction { bob > alice }` can be distinguished from `transaction { bob < alice }` (note the change in comparison operator). + +The information of comparison operator used can be obtained in one of two possible ways: +- You can check whether `<`/`<=` was used or `>`/`>=`. To do that, put the parameter of type `RankingComparisonInfo` between corresponding parameters in a function signature. It will receive the information about actually used operator. +- You can check whether `<`/`>` was used or `<=`/`>=`. To do that, put the parameter of type `StrictnessComparisonInfo` between corresponding parameters in a function signature. It will receive the information about actually used operator. + +Restrictions: +- if there is + - more than one comparison operations for the same types in a pattern + - these comparison operations are in the same *inferred lambda* +- then + - you can't mix `RankingComparisonInfo` and `StrictnessComparisonInfo` parameters for these operations in order to get actual used operator info + +#### Inclusion check +For example `groups { user in "admins" }` can be distinguished from `groups { user !in "admins" }` (note the negation of `in` operator). +Put the parameter of type `InclusionInfo` between corresponding parameters in a function signature to receive the information about actually used inclusion operator (`in` or `!in`). + +### Synthetic properties +All identifiers defined in a pattern which have no match with one of [target function](doc/targetFunctions.md) parameters will be created as synthetic properties to support the pattern. + +See `Example-000`, `Example-001`, `Example-003`, `Example-004` etc. + +### Minimum pollution principle +All generated declarations are placed in the most narrow scope (package, class, nested classes) to reduce conflicts probability and to maintain your namespaces as clean as possible. + +See `Example-016`. + +### Conflict detection +The processor is capable of detecting conflicts between parts of generated code (and resolving them in simple cases) as well as between generated code and Kotlin stdlib (in simple cases). Conflict detection between generated code and user code is not implemented yet. + +```kotlin +// Example-016 + +// declarations inside inferred lambda go into its scope (scope narrowing) +@GlobalPattern("name * age - { occupation * income }") +fun printPersonInfo(occupation: String, income: Int, name: String, age: Int) { + println("Person '$name'($age) is '$occupation'($income X)") +} + +// declarations go into one shared scope +@GlobalPattern("name % age - occupation % income") +fun printPersonInfo2(occupation: String, income: Int, name: String, age: Int) { + println("Person '$name'($age) is '$occupation'($income X)") +} + +fun main() { + // below are two equal expressions (with '*' syntax): + "Adam" * 20 - { "Physicist" * 1000 } + "Adam".times(age = 20) - { "Physicist".times(income = 1000) } // parameter names are conserved as conflict was avoided + + // below are two equal expressions (with '%' syntax): + "Adam" % 20 - "Physicist" % 1000 + "Adam".rem(int = 20) - "Physicist".rem(int = 1000) // parameter names aren't conserved as conflict was resolved +} +``` + +### Propagation of user given names +[Target function's](doc/targetFunctions.md) parameter names are propagated to corresponding generated declarations. In case of possible conflict resolution those user given names may not be preserved. + +See `Example-016`. + +### Implementation details hiding +Most implementation details (fields of holder classes, internal structure of context delegate class, etc.) are hidden from inadvertent usage. + +## Installation + +### Install KSP plugin +Make sure you have KSP plugin (`com.google.devtools.ksp`) available in your project. The KSP plugin version (`kspVersion`) must match Kotlin version used in your project. + +**settings.gradle** + +```gradle +pluginManagement { + repositories { + gradlePluginPortal() + } +} +``` + +**build.gradle** +```gradle +plugins { + id "com.google.devtools.ksp" version "$kspVersion" +} + +sourceSets.main { + java.srcDirs("build/generated/ksp/main/kotlin") + java.srcDirs("build/generated/ksp/main/java") +} +``` + +### Install Kabu +Add following maven repository: + +```gradle +maven { + url = "http://80.240.29.205:45011/artifactory/libs-release-local" + allowInsecureProtocol = true +} +``` + +Add following dependencies to your project: + +```gradle +def kabuVersion = '0.20.0' + +dependencies { + ksp "io.kabu:processor:$kabuVersion" + compileOnly "io.kabu:annotations:$kabuVersion" + + implementation "io.kabu:runtime:$kabuVersion +} +``` + +#### Processor options +Optional preferences can be set in a `build.gradle` file: + +```gradle +ksp { + arg("ksp.io.kabu.allowUnsafe", "true") +} +``` + +Available options: +- `ksp.io.kabu.allowUnsafe` + - "true" - *unsafe* features of patterns are enabled + - "false" - *unsafe* features of patterns are disabled + +## Diagnostics +There are cases when some patterns can not be implemented for you by the processor. Generally this could happen due to several reasons: +- syntactically incorrect pattern. If pattern does not follow [pattern syntax rules](doc/patternSyntax.md), the processor can not do anything with it - the runtime expression won't work anyway +- [target functions](doc/targetFunctions.md) with unsupported properties (e.g. `@LocalPattern` on a top level function) +- there are some conflicts with already existing declarations. For example one can not override an already defined member function/property of some type (Kotlin stdlib type or user type) + +Basically you should get a diagnostic message containing the location(s) of code which caused the error. If not - please file an issue for that. diff --git a/doc/exampleProject.md b/doc/exampleProject.md new file mode 100644 index 0000000..82bd821 --- /dev/null +++ b/doc/exampleProject.md @@ -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 +} +``` diff --git a/doc/patternExtension.md b/doc/patternExtension.md new file mode 100644 index 0000000..efdfe84 --- /dev/null +++ b/doc/patternExtension.md @@ -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=()` - where `` is the chosen name for context. `` 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=` - where `` 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=` - where `` 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, + val trophies: List +) + +class PlayersBuilder @ContextCreator("playersBuilder") constructor() { + val players = mutableListOf() + + @LocalPattern("name - number") + fun addPlayer(name: String, number: Int) { + players.add(Player(name, number)) + } +} + +class FootballTeamBuilder @ContextCreator("footballTeamBuilder") constructor() { + + val trophies = mutableListOf() + var isChampion = false + val players = mutableListOf() + + @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) +} +``` + diff --git a/doc/patternSyntax.md b/doc/patternSyntax.md new file mode 100644 index 0000000..7ecb4b7 --- /dev/null +++ b/doc/patternSyntax.md @@ -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. + diff --git a/doc/samples/access/data.json b/doc/samples/access/data.json new file mode 100644 index 0000000..b1351d9 --- /dev/null +++ b/doc/samples/access/data.json @@ -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" + } +] \ No newline at end of file diff --git a/doc/samples/assign/data.json b/doc/samples/assign/data.json new file mode 100644 index 0000000..30fca56 --- /dev/null +++ b/doc/samples/assign/data.json @@ -0,0 +1,152 @@ +[ + { + "raw" : "i { b.x = s } // i b s", + "sample" : "100 { false.x = \"abcdef\" }", + "termination" : "100, false, abcdef" + }, + { + "raw" : "i { b.x.y = s } // i b s", + "sample" : "100 { false.x.y = \"abcdef\" }", + "termination" : "100, false, abcdef" + }, + { + "raw" : "i { b.x.y = s.z } // i b s", + "sample" : "100 { false.x.y = \"abcdef\".z }", + "termination" : "100, false, abcdef" + }, + { + "raw" : "i { b[x,i1,y] = s } // i i1 b s", + "sample" : "11 { true[x, -5, y] = \"aaa\" }", + "termination" : "11, -5, true, aaa" + }, + { + "raw" : "i { b[x,y] = s } // i b s", + "sample" : "17 { false[x,y] = \"ttt\" }", + "termination" : "17, false, ttt" + }, + { + "raw" : "i { b[x,i1,y] = s } - { s2.x[y] = i2 } // i i1 b s s2 i2", + "sample" : "1 { false[x, 2, y] = \"aaa\" } - { \"bbb\".x[y] = 3 }", + "termination" : "1, 2, false, aaa, bbb, 3" + }, + { + "raw" : "x = s // s", + "sample" : "x = \"abcdef\"", + "termination" : "abcdef" + }, + { + "raw" : "x = y - s { z[zz] = i } // s i", + "sample" : "x = y - \"abc\" { z[zz] = 3 }", + "termination" : "abc, 3" + }, + { + "raw" : "x[b] = s // b s", + "sample" : "x[false] = \"abc\"", + "termination" : "false, abc" + }, + { + "raw" : "x[ {y} * i] = s // s i", + "sample" : "x[ {y} * 1] = \"abc\"", + "termination" : "abc, 1" + }, + { + "raw" : "x[ {y} * i, b1 - b2] = s // s i b1 b2", + "sample" : "x[{ y } * 1, false - true] = \"abc\"", + "termination" : "abc, 1, false, true" + }, + { + "raw" : "x = { z /= s } // s", + "sample" : "x = { z /= \"abc\" }", + "termination" : "abc" + }, + { + "raw" : "x.y = s // s", + "sample" : "x.y = \"abc\"", + "termination" : "abc" + }, + { + "raw" : "{ b}.x = s // s b", + "sample" : "{ true }.x = \"aaa\"", + "termination" : "aaa, true" + }, + { + "raw" : "{ b - {i} }.x = s // s b", + "sample" : "{ true - { i } }.x = \"aaa\"", + "termination" : "aaa, true" + }, + { + "raw" : "{ b - {i} }.x = s // s b i", + "sample" : "{ true - { 100 } }.x = \"aaa\"", + "termination" : "aaa, true, 100" + }, + { + "raw" : "i { y = b } // i b", + "sample" : "1 { y = true }", + "termination" : "1, true" + }, + { + "raw" : "z { y = b } // b", + "sample" : "z { y = false }", + "termination" : "false" + }, + { + "raw" : "z { y = b }.x = s // s b", + "sample" : "z { y = true }.x = \"abc\"", + "termination" : "abc, true" + }, + { + "raw" : "{i - b}.y = { z /= s } // i b s", + "sample" : "{111 - false}.y = { z /= \"abc\" }", + "termination" : "111, false, abc" + }, + { + "raw" : "x = { z /= !s } // s", + "sample" : "x = { z /= !\"abcdef\" }", + "termination" : "abcdef" + }, + { + "raw" : "x[ {y} * i, b] = { z /= !s } // s i b", + "sample" : "x[ {y} * 123, true] = { z /= !\"abcd\" }", + "termination" : "abcd, 123, true" + }, + { + "raw" : "x[ {y} * i, b] = { z /= { !s } } // s i b", + "sample" : "x[ {y} * 0, false] = { z /= { !\"abc\" } } // s i b", + "termination" : "abc, 0, false" + }, + { + "raw" : "x = s {y = i} // s i", + "sample" : "x = \"aaa\" {y = -1}", + "termination" : "aaa, -1" + }, + { + "raw" : "x[{y = i}] = s // s i", + "sample" : "x[{y = 100}] = \"abcdef\"", + "termination" : "abcdef, 100" + }, + { + "raw" : "x { b[x,y] /= s } // b s", + "sample" : "x { false[x, y] /= \"asdad\" }", + "termination" : "false, asdad" + }, + { + "raw" : "x { y /= b } // b", + "sample" : "x { y /= true }", + "termination" : "true" + }, + { + "raw" : "x { b /= y } // b", + "sample" : "x { true /= y }", + "termination" : "true" + }, + { + "raw" : "b[x] = s // b s", + "sample" : "false[x] = \"aaaa\"", + "termination" : "false, aaaa" + }, + { + "raw" : "i { b1[x] = s1 * { b2[x] = s2 } } // i b1 b2 s1 s2", + "sample" : "1 { false[x] = \"aaa\" * { true[x] = \"bbb\" } }", + "termination" : "1, false, true, aaa, bbb" + } +] \ No newline at end of file diff --git a/doc/samples/binary/data.json b/doc/samples/binary/data.json new file mode 100644 index 0000000..c3a7374 --- /dev/null +++ b/doc/samples/binary/data.json @@ -0,0 +1,122 @@ +[ + { + "raw" : "s-b // s b", + "sample" : "\"abc\" - false", + "termination" : "abc, false" + }, + { + "raw" : "b+s // b s", + "sample" : "true + \"abc\"", + "termination" : "true, abc" + }, + { + "raw" : "b+s1 - (i+ s2) // b s1 i s2 ", + "sample" : "false + \"abc\" - (43+\"def\")", + "termination" : "false, abc, 43, def" + }, + { + "raw" : "s1-b + i -s2 // s1 b i s2", + "sample" : "\"qwerty\"-true + 23 -\"def\"", + "termination" : "qwerty, true, 23, def" + }, + { + "raw" : "s * b // s b", + "sample" : "\"abc\" * true", + "termination" : "abc, true" + }, + { + "raw" : "s/b // s b", + "sample" : "\"def\"/false", + "termination" : "def, false" + }, + { + "raw" : "s %b // s b", + "sample" : "\"abc\" %true", + "termination" : "abc, true" + }, + { + "raw" : "s %b / i *s2% b2 // s b i s2 b2", + "sample" : "\"cdef\" %false / 41 *\"abc\"% true", + "termination" : "cdef, false, 41, abc, true" + }, + { + "raw" : "s *b % (i *s2)% b2 // s b i s2 b2", + "sample" : "\"aaa\" *true % (17 *\"bbb\")% false", + "termination" : "aaa, true, 17, bbb, false" + }, + { + "raw" : "s..b // s b", + "sample" : "\"aaa\"..false", + "termination" : "aaa, false" + }, + { + "raw" : "s..b + i // s b i", + "sample" : "\"bbb\"..true + 13", + "termination" : "bbb, true, 13" + }, + { + "raw" : "s..b ..i // s b i", + "sample" : "\"abc\"..false ..19", + "termination" : "abc, false, 19" + }, + { + "raw" : "s..b ..i .. s2 // s b i s2", + "sample" : "\"bbb\"..true ..7 .. \"ccc\"", + "termination" : "bbb, true, 7, ccc" + }, + { + "raw" : "s func b // s b", + "sample" : "\"abc\" func false", + "termination" : "abc, false" + }, + { + "raw" : "s * b func i // s b i", + "sample" : "\"abcdef\" * true func 11", + "termination" : "abcdef, true, 11" + }, + { + "raw" : "b func i + s // b i s", + "sample" : "true func 37 + \"abc\"", + "termination" : "true, 37, abc" + }, + { + "raw" : "b func i func2 s // b i s", + "sample" : "false func 3 func2 \"aaa\"", + "termination" : "false, 3, aaa" + }, + { + "raw" : "b func i func2 (s func3 b2) // b i s b2", + "sample" : "true func 5 func2 (\"abcdef\" func3 false)", + "termination" : "true, 5, abcdef, false" + }, + { + "raw" : "s* b+ i // s b i", + "sample" : "\"qwerty\"* true+ 100", + "termination" : "qwerty, true, 100" + }, + { + "raw" : "(s * b + i / s2) % b2 + i2 // s b i s2 b2 i2", + "sample" : "(\"abc\" * false + 0 / \"def\") % true + 43", + "termination" : "abc, false, 0, def, true, 43" + }, + { + "raw" : "b % (s .. b2 + i / s2) // b s b2 i s2", + "sample" : "false % (\"qwerty\" .. true + 7 / \"abc\")", + "termination" : "false, qwerty, true, 7, abc" + }, + { + "raw" : "(s .. b * i ) % b2 + i2 .. s2 // s b i b2 i2 s2", + "sample" : "(\"aaa\" .. false * 9 ) % true + 53 .. \"bbb\"", + "termination" : "aaa, false, 9, true, 53, bbb" + }, + { + "raw" : "s..i() // s i", + "sample" : "\"abc\"..1001()", + "termination" : "abc, 1001" + }, + { + "raw": "x1..x2()", + "sample": "x1..x2()", + "termination": "" + } +] diff --git a/doc/samples/call/data.json b/doc/samples/call/data.json new file mode 100644 index 0000000..224603a --- /dev/null +++ b/doc/samples/call/data.json @@ -0,0 +1,47 @@ +[ + { + "raw" : "b() // b", + "sample" : "false()", + "termination" : "false" + }, + { + "raw" : "s(b,i) // s b i", + "sample" : "\"abc\"(true,11)", + "termination" : "abc, true, 11" + }, + { + "raw" : "s (b, i, s2,b2) // s b i s2 b2", + "sample" : "\"aaa\" (false, 5, \"bbb\",true)", + "termination" : "aaa, false, 5, bbb, true" + }, + { + "raw" : "s(b, i) { s2 } // s b i s2", + "sample" : "\"abc\"(true, 7) { \"def\" }", + "termination" : "abc, true, 7, def" + }, + { + "raw" : "{s }{ b} // s b", + "sample" : "{ \"aaa\" }{ true }", + "termination" : "aaa, true" + }, + { + "raw" : "{}{ b} // b", + "sample" : "{}{ false }", + "termination" : "false" + }, + { + "raw" : "{s }{} // s", + "sample" : "{ \"abc\" }{}", + "termination" : "abc" + }, + { + "raw" : "-b() // b", + "sample" : "-true()", + "termination" : "true" + }, + { + "raw" : "(s)(b)(i) // s b i", + "sample" : "(\"abc\")(true)(23)", + "termination" : "abc, true, 23" + } +] \ No newline at end of file diff --git a/doc/samples/captures/data.json b/doc/samples/captures/data.json new file mode 100644 index 0000000..d4bf3f0 --- /dev/null +++ b/doc/samples/captures/data.json @@ -0,0 +1,187 @@ +[ + { + "raw" : "s{ b in b2 * i } // s b incl b2 i", + "sample" : "\"abcdef\"{ false !in true * 53 }", + "termination" : "abcdef, false, NOT_IN, true, 53" + }, + { + "raw" : "i{ (b in b2) + (s < i2) } // i b incl b2 s rank i2", + "sample" : "41{ (false !in true) + (\"abc\" > 4) }", + "termination" : "41, false, NOT_IN, true, abc, GREATER, 4" + }, + { + "raw" : "!i{ (s > b) * s2 } // i s rank b s2", + "sample" : "!101{ (\"abc\" > false) * \"def\" }", + "termination" : "101, abc, GREATER, false, def" + }, + { + "raw" : "!i{ (s > b) * (s2 < b2) } // i s rank b s2 rank2 b2", + "sample" : "!45{ (\"qwerty\" < true) * (\"def\" < false) }", + "termination" : "45, qwerty, LESS, true, def, LESS, false" + }, + { + "raw" : "!i{ (s > b > s2) / i2 } // i s rank b rank2 s2 i2", + "sample" : "!11{ (\"aaa\" < true > \"bbb\") / 43 }", + "termination" : "11, aaa, LESS, true, GREATER, bbb, 43" + }, + { + "raw" : "!i{ +(b in i2) } // i b incl i2", + "sample" : "!21{ +(true in 2) }", + "termination" : "21, true, IN, 2" + }, + { + "raw" : "!i{ +(b in b2 in i2) } // i b incl b2 incl2 i2", + "sample" : "!0{ +(false in true !in 1) }", + "termination" : "0, false, IN, true, NOT_IN, 1" + }, + { + "raw" : "!i{ (b in b2) * s } // i b incl b2 s", + "sample" : "!4{ (true !in false) * \"qwerty\" }", + "termination" : "4, true, NOT_IN, false, qwerty" + }, + { + "raw" : "!i{ -(s > (b > i2 > s2)) } // i s rank b rank2 i2 rank3 s2", + "sample" : "!88{ -(\"abc\" > (true < 23 < \"string\")) }", + "termination" : "88, abc, GREATER, true, LESS, 23, LESS, string" + }, + { + "raw" : "!i{ -(s > b > i2 > s2) } // i s rank1 b rank2 i2 rank3 s2", + "sample" : "!99{ -(\"a\" > true > 7 > \"b\") }", + "termination" : "99, a, GREATER, true, GREATER, 7, GREATER, b" + }, + { + "raw" : "!i{ (s > b > s2) / i2 } // i s rank1 b rank2 s2 i2", + "sample" : "!19{ (\"abc\" < false < \"def\") / 37 }", + "termination" : "19, abc, LESS, false, LESS, def, 37" + }, + { + "raw" : "!i{ +(b in b2 in i2 < s2 voila i3) } // i b incl b2 incl2 i2 rank s2 i3", + "sample" : "!17{ +(true in false !in 0 > \"ggg\" voila 5) }", + "termination" : "17, true, IN, false, NOT_IN, 0, GREATER, ggg, 5" + }, + { + "raw" : "i{ s < b } // i s rank b", + "sample" : "7{ \"qwerty\" < true }", + "termination" : "7, qwerty, LESS, true" + }, + { + "raw" : "i{ b in b2 } // i b incl b2", + "sample" : "3{ false !in true }", + "termination" : "3, false, NOT_IN, true" + }, + { + "raw" : "i{ {b in b2} } // i b incl b2", + "sample" : "6{ {false in true} }", + "termination" : "6, false, IN, true" + }, + { + "raw" : "i{ {{b in b2}} } // i b incl b2", + "sample" : "99{ {{true !in true}} }", + "termination" : "99, true, NOT_IN, true" + }, + { + "raw" : "i{b in b2} * s // i b incl b2 s", + "sample" : "21{false !in false} * \"abc\"", + "termination" : "21, false, NOT_IN, false, abc" + }, + { + "raw" : "s{b in b2} * { i % s2 } // s b incl b2 i s2", + "sample" : "\"aaa\"{true in false} * { 222 % \"bbb\" }", + "termination" : "aaa, true, IN, false, 222, bbb" + }, + { + "raw" : "i { { s } > i2 } // i s rank i2", + "sample" : "12 { { \"abc\" } > 3 }", + "termination" : "12, abc, GREATER, 3" + }, + { + "raw" : "s1{ s2 * b < i } // s1 s2 b i", + "sample" : "\"abc\"{ \"def\" * false > 17 } // s1 s2 b i", + "termination" : "abc, def, false, 17" + }, + { + "raw" : "s { b !in b2 * i } // s b incl b2 i", + "sample" : "\"abcdef\"{ false !in true * 53 }", + "termination" : "abcdef, false, NOT_IN, true, 53" + }, + { + "raw" : "x { { b1 !in b2 } * i } //b1 incl b2 i", + "sample" : "x { { false !in true } * 53 }", + "termination" : "false, NOT_IN, true, 53" + }, + { + "raw" : "s{ b in b2 * i } // s b incl b2 i", + "sample" : "\"abc\"{ true in false * 11 }", + "termination" : "abc, true, IN, false, 11" + }, + { + "raw" : "!s{ {b in i} * b2 } // s b incl i b2", + "sample" : "!\"aaa\"{ {true in 123} * false }", + "termination" : "aaa, true, IN, 123, false" + }, + { + "raw" : "!i{ {b1 in b2} * { i2 % s } } // i b1 incl b2 i2 s", + "sample" : "!0{ {false in false} * { 1001 % \"abc\" } }", + "termination" : "0, false, IN, false, 1001, abc" + }, + { + "raw" : "x1 { b < s } * { s2 < b2 } // b rank s s2 strict b2", + "sample" : "x1 { true < \"abc\" } * { \"def\" < false }", + "termination" : "true, LESS, abc, def, STRICT, false" + }, + { + "raw" : "i{ b * { i2 in i3 } } // i b i2 incl2 i3", + "sample" : "11{ true * { 3 in 5 } }", + "termination" : "11, true, 3, IN, 5" + }, + { + "raw" : "i{ {b} * { i2 in i3 } } // i b i2 incl2 i3", + "sample" : "1{ {false} * { 3 in 4 } }", + "termination" : "1, false, 3, IN, 4" + }, + { + "raw" : "i{ {b in b2} * { i2 in i3 } } // i b incl1 b2 i2 incl2 i3", + "sample" : "1{ {false in true} * { 3 in 4 } }", + "termination" : "1, false, IN, true, 3, IN, 4" + }, + { + "raw" : "i{ {b in b2} * { i2 in i3 < s } } // i b incl1 b2 i2 incl2 i3 s", + "sample" : "1{ {false in true} * { 3 in 4 > \"abc\"} }", + "termination" : "1, false, IN, true, 3, IN, 4, abc" + }, + { + "raw" : "i{ +( { i2 in i3 } ) } // i i2 incl2 i3", + "sample" : "1{ +( { 3 in 4 } ) }", + "termination" : "1, 3, IN, 4" + }, + { + "raw" : "x { { b > s } > i } // b s i", + "sample" : "x { { true > \"abc\" } > 11 } // b s i", + "termination" : "true, abc, 11" + }, + { + "raw" : "x { i < { b > s } } // i b s", + "sample" : "x { 1 < { false > \"aaa\" } } // i b s", + "termination" : "1, false, aaa" + }, + { + "raw" : "x { s / b < { b2 > s2 } } // s b b2 s2", + "sample" : "x { \"aaa\" / true < { false > \"bbb\" } } // s b b2 s2", + "termination" : "aaa, true, false, bbb" + }, + { + "raw" : "x { { b2 > s2 } in s / b } // b2 s2 s b", + "sample" : "x { { true > \"abc\" } in \"xyz\" / false }", + "termination" : "true, abc, xyz, false" + }, + { + "raw" : "x { s / b in y{ b2 > s2 } } // s b b2 s2", + "sample" : "x { \"abc\" / false in y{ true > \"xyz\" } }", + "termination" : "abc, false, true, xyz" + }, + { + "raw" : "x { s / b in { b2 > s2 } } // s b b2 s2", + "sample" : "x { \"abc\" / false in { true > \"xyz\" } }", + "termination" : "abc, false, true, xyz" + } +] \ No newline at end of file diff --git a/doc/samples/complexUserTypes/data.json b/doc/samples/complexUserTypes/data.json new file mode 100644 index 0000000..556912d --- /dev/null +++ b/doc/samples/complexUserTypes/data.json @@ -0,0 +1,7 @@ +[ + { + "raw" : "i IF fs ELSE fs2 // i fs fs2", + "sample" : "1 IF { \"abc\" } ELSE { \"def\" }", + "termination" : "1, () -> kotlin.String, () -> kotlin.String" + } +] \ No newline at end of file diff --git a/doc/samples/conflicts/data.json b/doc/samples/conflicts/data.json new file mode 100644 index 0000000..93858bb --- /dev/null +++ b/doc/samples/conflicts/data.json @@ -0,0 +1,62 @@ +[ + { + "raw" : "s * b + s2 * b2 // s b s2 b2", + "sample" : "\"abc\" * false + \"cde\" * true", + "termination" : "abc, false, cde, true" + }, + { + "raw" : "{s * b} + {s2 * b2} // s b s2 b2", + "sample" : "{\"aaa\" * false} + {\"bbb\" * true} // s b s2 b2", + "termination" : "aaa, false, bbb, true" + }, + { + "raw" : "{{s * b}} + {{s2 * b2}} // s b s2 b2", + "sample" : "{{\"aaa\" * false}} + {{\"bbb\" * true}} // s b s2 b2", + "termination" : "aaa, false, bbb, true" + }, + { + "raw" : "s * b + s2 * b2 + s3 * b3 // s b s2 b2 s3 b3", + "sample" : "\"aaa\" * false + \"bbb\" * true + \"ccc\" * false // s b s2 b2", + "termination" : "aaa, false, bbb, true, ccc, false" + }, + { + "raw" : "{ s % i } * b - x({ s2 % i2 } * b2) // s i b s2 i2 b2", + "sample" : "{ \"abc\" % 1 } * false - x({ \"cde\" % 333 } * true)", + "termination" : "abc, 1, false, cde, 333, true" + }, + { + "raw" : "{ s % i } * b - x({ s2 % i2 } * b2) + y({ s3 % i3 } * b3) // s i b s2 i2 b2 s3 i3 b3", + "sample" : "{ \"abc\" % 1 } * false - x({ \"cde\" % 333 } * true) + y({ \"ghi\" % 444 } * false)", + "termination" : "abc, 1, false, cde, 333, true, ghi, 444, false" + }, + { + "raw" : "x X i X x // i", + "sample" : "x X 5 X x", + "termination" : "5" + }, + { + "raw" : "i * b - x(i2 * b2) // i b i2 b2", + "sample" : "100 * true - x(1 * false)", + "termination" : "100, true, 1, false" + }, + { + "raw" : "x(s * b, s2 * b2) + y(s3 * b3, s4 * b4) // s b s2 b2 s3 b3 s4 b4", + "sample" : "x(\"aaa\" * false, \"bbb\" * true) + y(\"ccc\" * false, \"ddd\" * true)", + "termination" : "aaa, false, bbb, true, ccc, false, ddd, true" + }, + { + "raw" : "x(s * b, s2 * b2) + x(s3 * b3, s4 * b4) // s b s2 b2 s3 b3 s4 b4", + "sample" : "x(\"aaa\" * false, \"bbb\" * true) + x(\"ccc\" * false, \"ddd\" * true)", + "termination" : "aaa, false, bbb, true, ccc, false, ddd, true" + }, + { + "raw" : "xxx(s * b) + s2 * b2 + s3 * b3 // s b s2 b2 s3 b3", + "sample" : "xxx(\"aaa\" * false) + \"bbb\" * true + \"ccc\" * false", + "termination" : "aaa, false, bbb, true, ccc, false" + }, + { + "raw" : "{ W h y } S o { S e r i o u s }", + "sample" : "{ W h y } S o { S e r i o u s }", + "termination" : "" + } +] \ No newline at end of file diff --git a/doc/samples/indexing/data.json b/doc/samples/indexing/data.json new file mode 100644 index 0000000..4c96956 --- /dev/null +++ b/doc/samples/indexing/data.json @@ -0,0 +1,22 @@ +[ + { + "raw" : "s[b] // s b", + "sample" : "\"abc\"[true]", + "termination" : "abc, true" + }, + { + "raw" : "s[b, i,s2] // s b i s2", + "sample" : "\"abc\"[false, 13,\"def\"]", + "termination" : "abc, false, 13, def" + }, + { + "raw" : "s[b, i,s2][b2] // s b i s2 b2", + "sample" : "\"aaa\"[true, 11,\"bbb\"][false]", + "termination" : "aaa, true, 11, bbb, false" + }, + { + "raw" : "s[b, i1[i2[b2]],s2] // s b i1 i2 b2 s2", + "sample" : "\"abc\"[false, 23[17[true]],\"def\"]", + "termination" : "abc, false, 23, 17, true, def" + } +] \ No newline at end of file diff --git a/doc/samples/lambda/data.json b/doc/samples/lambda/data.json new file mode 100644 index 0000000..4a95222 --- /dev/null +++ b/doc/samples/lambda/data.json @@ -0,0 +1,52 @@ +[ + { + "raw" : "!{s} // s", + "sample" : "!{ \"abc\" }", + "termination" : "abc" + }, + { + "raw" : "!{!s} // s", + "sample" : "!{! \"aaa\"}", + "termination" : "aaa" + }, + { + "raw" : "s {} // s", + "sample" : "\"abcdef\" {}", + "termination" : "abcdef" + }, + { + "raw" : "s { b / i } // s b i", + "sample" : "\"abc\" { true / 3 }", + "termination" : "abc, true, 3" + }, + { + "raw" : "s(b) { i + s2 } // s b i s2", + "sample" : "\"abc\"(false) { 7 + \"def\" }", + "termination" : "abc, false, 7, def" + }, + { + "raw" : "s func { b * i } // s b i", + "sample" : "\"aaa\" func { false * 29 }", + "termination" : "aaa, false, 29" + }, + { + "raw" : "s({b},i) {s2} // s b i s2", + "sample" : "\"abc\"({true},11) {\"def\"}", + "termination" : "abc, true, 11, def" + }, + { + "raw" : "+{ i } // i", + "sample" : "+{ 17 }", + "termination" : "17" + }, + { + "raw" : "{ s } .. i // s i", + "sample" : "{ \"abc\" } .. 5", + "termination" : "abc, 5" + }, + { + "raw" : "{}{}", + "sample" : "{}{}", + "termination" : "" + } +] \ No newline at end of file diff --git a/doc/samples/misc/data.json b/doc/samples/misc/data.json new file mode 100644 index 0000000..1203680 --- /dev/null +++ b/doc/samples/misc/data.json @@ -0,0 +1,197 @@ +[ + { + "raw" : "b - i[b2 withOption i2, s1 % s2] // b i b2 i2 s1 s2", + "sample" : "false - 31[true withOption 2, \"abc\" % \"def\"]", + "termination" : "false, 31, true, 2, abc, def" + }, + { + "raw" : "s(b / i % s2, b2-i2*s3) // s b i s2 b2 i2 s3", + "sample" : "\"abc\" (true / 7 % \"def\", false - 29*\"qwerty\")", + "termination" : "abc, true, 7, def, false, 29, qwerty" + }, + { + "raw" : "b % (s .. b2 withOption s2 * i1 / s3) withSome i2 // b s b2 s2 i1 s3 i2", + "sample" : "false % (\"abcdef\" .. false withOption \"def\" * 11 / \"qweerty\") withSome 91", + "termination" : "false, abcdef, false, def, 11, qweerty, 91" + }, + { + "raw" : "s / b * i + b2 // s b i b2", + "sample" : "\"aaa\" / false * 23 + false", + "termination" : "aaa, false, 23, false" + }, + { + "raw" : "s * b / i // s b i", + "sample" : "\"abc\" * true / 13", + "termination" : "abc, true, 13" + }, + { + "raw" : "s[b - i * s2, b2 withOption i2 % s3] // s b i s2 b2 i2 s3", + "sample" : "\"abc\"[false - 17 * \"def\", true withOption 9 % \"ghjk\"]", + "termination" : "abc, false, 17, def, true, 9, ghjk" + }, + { + "raw" : "!i on -s / +-b // i s b", + "sample" : "!3 on -\"abc\" / +-true", + "termination" : "3, abc, true" + }, + { + "raw" : "-s / +-b // s b", + "sample" : "-\"def\" / +-false", + "termination" : "def, false" + }, + { + "raw" : "-s / +b // s b", + "sample" : "-\"aaa\" / +true", + "termination" : "aaa, true" + }, + { + "raw" : "-s // s", + "sample" : "-\"qwerty\"", + "termination" : "qwerty" + }, + { + "raw" : "-+!+-s // s", + "sample" : "-+!+-\"abcdef\"", + "termination" : "abcdef" + }, + { + "raw" : "!!s - !!i // s i", + "sample" : "!!\"aaa\" - !!81", + "termination" : "aaa, 81" + }, + { + "raw" : "-s .. -b // s b", + "sample" : "-\"abc\" .. -false", + "termination" : "abc, false" + }, + { + "raw" : "!s[+b approved i(s2 % b2)] // s b i s2 b2", + "sample" : "!\"def\"[+false approved 22(\"abc\" % true)]", + "termination" : "def, false, 22, abc, true" + }, + { + "raw" : "{} { s } // s", + "sample" : "{} { \"abcdef\" }", + "termination" : "abcdef" + }, + { + "raw" : "{} { s % b } // s b", + "sample" : "{} { \"abc\" % true }", + "termination" : "abc, true" + }, + { + "raw" : "{}[s,b,i] // s b i", + "sample" : "{}[\"aaa\",false,21]", + "termination" : "aaa, false, 21" + }, + { + "raw" : "s({}) // s", + "sample" : "\"abc\"({})", + "termination" : "abc" + }, + { + "raw" : "s{} // s", + "sample" : "\"abcdef\"{}", + "termination" : "abcdef" + }, + { + "raw" : "s({},{}) // s", + "sample" : "\"qwerty\"({},{})", + "termination" : "qwerty" + }, + { + "raw" : "s[{ b[{},{}] }, {i}] // s b i", + "sample" : "\"abc\"[{ false[{},{}] }, {1}]", + "termination" : "abc, false, 1" + }, + { + "raw" : "!{}[s,b,i] // s b i", + "sample" : "!{}[\"poiuy\",true, 12]", + "termination" : "poiuy, true, 12" + }, + { + "raw" : "!{}[i]() // i", + "sample" : "!{}[23]()", + "termination" : "23" + }, + { + "raw" : "!{}[i] // i", + "sample" : "!{}[7]", + "termination" : "7" + }, + { + "raw" : "!{ s / b } * { i } // s b i", + "sample" : "!{ \"abc\" / false } * { 87 }", + "termination" : "abc, false, 87" + }, + { + "raw" : "!{ -s / b option +s2 } * { i } // s b s2 i", + "sample" : "!{ -\"def\" / true option +\"abc\" } * { 19 }", + "termination" : "def, true, abc, 19" + }, + { + "raw" : "!{{ s % b } - { i abc s2 }} // s b i s2", + "sample" : "!{{ \"abc\" % false } - { 12 abc \"def\" }}", + "termination" : "abc, false, 12, def" + }, + { + "raw" : "!{{{ b }}} // b", + "sample" : "!{{{ false }}}", + "termination" : "false" + }, + { + "raw" : "{{{ b }}}(i) // b i", + "sample" : "{{{ true }}}(0)", + "termination" : "true, 0" + }, + { + "raw" : "+{{ b }[s,i](b2)} // b s i b2", + "sample" : "+{{ false }[\"abc\",55](true)}", + "termination" : "false, abc, 55, true" + }, + { + "raw" : "b[i]() // b i", + "sample" : "true[3]()", + "termination" : "true, 3" + }, + { + "raw" : "b[i](s) // b i s", + "sample" : "false[9](\"Messi\")", + "termination" : "false, 9, Messi" + }, + { + "raw" : "!i{ { { i2 % s } } } // i i2 s", + "sample" : "!2{ { { 5 % \"five\" } } }", + "termination" : "2, 5, five" + }, + { + "raw" : "i{ i2 % s } // i i2 s", + "sample" : "1001{ 101 % \"course\" }", + "termination" : "1001, 101, course" + }, + { + "raw" : "i infixTest { b } // i b", + "sample" : "19 infixTest { true }", + "termination" : "19, true" + }, + { + "raw" : "!i{ { i2 % s } } // i i2 s", + "sample" : "!123{ { 4 % \"aabsd\" } }", + "termination" : "123, 4, aabsd" + }, + { + "raw" : "!{{ b }} // b", + "sample" : "!{{ false }}", + "termination" : "false" + }, + { + "raw" : "x1 { x2 { (b + s) *= i } x3 { b2 -= s2 } } // b s i b2 s2", + "sample" : "x1 { x2 { true + \"abc\" *= 23 } x3 { false -= \"def\" } }", + "termination" : "true, abc, 23, false, def" + }, + { + "raw" : "x { s1[b1 > i1, b2 < i2] } // s1 b1 rank i1 b2 rank2 i2", + "sample" : "x { \"abc\"[false > 11, true < 101] }", + "termination" : "abc, false, GREATER, 11, true, LESS, 101" + } +] \ No newline at end of file diff --git a/doc/samples/modAssign/data.json b/doc/samples/modAssign/data.json new file mode 100644 index 0000000..93cbfed --- /dev/null +++ b/doc/samples/modAssign/data.json @@ -0,0 +1,92 @@ +[ + { + "raw" : "b += s // b s", + "sample" : "true += \"abc\"", + "termination" : "true, abc" + }, + { + "raw" : "b -= s // b s", + "sample" : "true -= \"aaa\"", + "termination" : "true, aaa" + }, + { + "raw" : "b *= s // b s", + "sample" : "true *= \"aaa\"", + "termination" : "true, aaa" + }, + { + "raw" : "b /= i // b i", + "sample" : "true /= 31", + "termination" : "true, 31" + }, + { + "raw" : "s %= i // s i", + "sample" : "\"abcdef\" %= 31", + "termination" : "abcdef, 31" + }, + { + "raw" : "b += s * i // b s i", + "sample" : "true += \"abc\" * 11", + "termination" : "true, abc, 11" + }, + { + "raw" : "b += { s } // b s", + "sample" : "true += { \"abcdef\" }", + "termination" : "true, abcdef" + }, + { + "raw" : "b[i] += s // b i s", + "sample" : "false[9] += \"Leo\"", + "termination" : "false, 9, Leo" + }, + { + "raw" : "b(i) += s // b i s", + "sample" : "false(9) += \"Leo\"", + "termination" : "false, 9, Leo" + }, + { + "raw" : "(b / i) += s // b i s", + "sample" : "(false / 0) += \"with parentheses\"", + "termination" : "false, 0, with parentheses" + }, + { + "raw" : "(b / i) += s // b i s", + "sample" : "false / 0 += \"without parentheses\"", + "termination" : "false, 0, without parentheses" + }, + { + "raw" : "{ b * i } += { s } // b i s", + "sample" : "{ true * 1 } += { \"aaa\" }", + "termination" : "true, 1, aaa" + }, + { + "raw" : "(b / i) /= s / s2 // b i s s2", + "sample" : "(true / 0) /= \"path\" / \"file\"", + "termination" : "true, 0, path, file" + }, + { + "raw" : "(b / i) /= s / s2 // b i s s2", + "sample" : "true / 0 /= \"path\" / \"file\"", + "termination" : "true, 0, path, file" + }, + { + "raw" : "x { s += i } x { b += s2 } // s i b s2", + "sample" : "x { \"abc\" += 3 } x { true += \"def\" }", + "termination" : "abc, 3, true, def" + }, + { + "raw" : "x { b %= i } // b i", + "sample" : "x { true %= 1 }", + "termination" : "true, 1" + }, + { + "raw" : "x { (b + s) %= i } // b s i", + "sample" : "x { true + \"abc\" %= 23 }", + "termination" : "true, abc, 23" + }, + { + "raw" : "x1 { x2 {(b + s) *= i } x3 { b2 -= s2 } } // b s i b2 s2", + "sample" : "x1 { x2 { true + \"abc\" *= 23 } x3 { false -= \"def\" } }", + "termination" : "true, abc, 23, false, def" + } +] \ No newline at end of file diff --git a/doc/samples/nullability/data.json b/doc/samples/nullability/data.json new file mode 100644 index 0000000..1a205d8 --- /dev/null +++ b/doc/samples/nullability/data.json @@ -0,0 +1,22 @@ +[ + { + "raw" : "i1 + s1 // i1? s1?", + "sample" : "3 + \"abc\"", + "termination" : "3, abc" + }, + { + "raw" : "i1 + s1 // i1? s1?", + "sample" : "null + null", + "termination" : "null, null" + }, + { + "raw" : "i1 + s1 // i1? s1?", + "sample" : "3 + null", + "termination" : "3, null" + }, + { + "raw" : "-!i // i?", + "sample" : "-!30", + "termination" : "30" + } +] \ No newline at end of file diff --git a/doc/samples/standardTermination/data.json b/doc/samples/standardTermination/data.json new file mode 100644 index 0000000..f8ad8cf --- /dev/null +++ b/doc/samples/standardTermination/data.json @@ -0,0 +1,102 @@ +[ + { + "raw" : "x1 { (b < s) + (b2 > s2) } // b s b2 s2", + "sample" : "x1 { (false > \"aaa\") + (true >= \"ddd\") }", + "termination" : "false, aaa, true, ddd" + }, + { + "raw" : "x1 { (b < s) + (b2 > s2) } // b rank s b2 s2", + "sample" : "x1 { (false > \"aaa\") + (true >= \"ddd\") }", + "termination" : "false, GREATER, aaa, true, ddd" + }, + { + "raw" : "x1 { (b < s) + (b2 > s2) } // b s b2 rank s2", + "sample" : "x1 { (false > \"aaa\") + (true < \"ddd\") }", + "termination" : "false, aaa, true, LESS, ddd" + }, + { + "raw" : "x1 { (b < s) + (b2 > s2) } // b rank1 s b2 rank2 s2", + "sample" : "x1 { (false < \"aaa\") + (true > \"ddd\") }", + "termination" : "false, LESS, aaa, true, GREATER, ddd" + }, + { + "raw" : "x1 { b in i in b2 in s } // b i b2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, 3, true, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b incl i b2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, NOT_IN, 3, true, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b i incl b2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, 3, IN, true, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b i b2 incl s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, 3, true, NOT_IN, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b incl1 i incl2 b2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, NOT_IN, 3, IN, true, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b incl1 i b2 incl2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, NOT_IN, 3, true, NOT_IN, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b i incl b2 incl2 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, 3, IN, true, NOT_IN, abc" + }, + { + "raw" : "x1 { b in i in b2 in s } // b incl1 i incl2 b2 incl3 s", + "sample" : "x1 { false !in 3 in true !in \"abc\" }", + "termination" : "false, NOT_IN, 3, IN, true, NOT_IN, abc" + }, + { + "raw" : "x1 { b <= s } // b strict s", + "sample" : "x1 { true <= \"aaa\" }", + "termination" : "true, RELAXED, aaa" + }, + { + "raw" : "s * i1 - b % i2 // b i2 s i1", + "sample" : "\"abc\" * 12 - true % 53", + "termination" : "true, 53, abc, 12" + }, + { + "raw" : "b < s // b s", + "sample" : "true > \"abc\"", + "termination" : "true, abc" + }, + { + "raw" : "b in s // b s", + "sample" : "false in \"aaa\"", + "termination" : "false, aaa" + }, + { + "raw" : "b !in s { s2 < i2 } // b s s2 rank i2", + "sample" : "false in \"abc\" { \"def\" > 3 }", + "termination" : "false, abc, def, GREATER, 3" + }, + { + "raw" : "xxx > s // s", + "sample" : "xxx > \"abcdef\"", + "termination" : "abcdef" + }, + { + "raw" : "Alice > s // s", + "sample" : "Alice > \"Hello!\"", + "termination" : "Hello!" + }, + { + "raw" : "Bob < s // s", + "sample" : "Bob < \"Hi!\"", + "termination" : "Hi!" + } +] \ No newline at end of file diff --git a/doc/samples/syntheticVars/data.json b/doc/samples/syntheticVars/data.json new file mode 100644 index 0000000..ce82083 --- /dev/null +++ b/doc/samples/syntheticVars/data.json @@ -0,0 +1,152 @@ +[ + { + "raw" : "synthetic % s // s", + "sample" : "synthetic % \"abc\"", + "termination" : "abc" + }, + { + "raw" : "synthetic vars are { s } // s", + "sample" : "synthetic vars are { \"awesome\" }", + "termination" : "awesome" + }, + { + "raw" : "synthetic vars are - s // s", + "sample" : "synthetic vars are - \"awesome\"", + "termination" : "awesome" + }, + { + "raw" : "iff(b) take { s } // b s", + "sample" : "iff(false) take { \"abc\" }", + "termination" : "false, abc" + }, + { + "raw" : "syn + b // b", + "sample" : "syn + true", + "termination" : "true" + }, + { + "raw" : "(b + x1) / x2 // b", + "sample" : "(false + x1) / x2", + "termination" : "false" + }, + { + "raw" : "(x1 + x2) / x3 % s % x4 // s", + "sample" : "(x1 + x2) / x3 % \"asdad\" % x4", + "termination" : "asdad" + }, + { + "raw" : "(x1 + x2) / x3 % (s % (x4 * x5)) // s", + "sample" : "(x1 + x2) / x3 % (\"asdad\" % (x4 * x5))", + "termination" : "asdad" + }, + { + "raw" : "x1[s] // s", + "sample" : "x1[\"abc\"]", + "termination" : "abc" + }, + { + "raw" : "i[x1] // i", + "sample" : "13[x1]", + "termination" : "13" + }, + { + "raw" : "i[x1, x2, x3] // i", + "sample" : "11[x1, x2, x3]", + "termination" : "11" + }, + { + "raw" : "x1(i, x2) // i", + "sample" : "x1(37, x2)", + "termination" : "37" + }, + { + "raw" : "i { x2 } // i", + "sample" : "7 { x2 }", + "termination" : "7" + }, + { + "raw" : "x1 { s } // s", + "sample" : "x1 { \"abc\" }", + "termination" : "abc" + }, + { + "raw" : "x1 {{{ s }}} // s", + "sample" : "x1 {{{ \"abc\" }}}", + "termination" : "abc" + }, + { + "raw" : "b {{{ x1 }}} // b", + "sample" : "true {{{ x1 }}}", + "termination" : "true" + }, + { + "raw" : "!x1 + { x2(i[x3{s(x4[b])}]) } // i s b", + "sample" : "!x1 + { x2(100[x3{\"abc\"(x4[true])}]) }", + "termination" : "100, abc, true" + }, + { + "raw" : "(!x1 + -x2) * !x3 % i // i", + "sample" : "(!x1 + -x2) * !x3 % 11", + "termination" : "11" + }, + { + "raw" : "expression full of synthetic variables", + "sample" : "expression full of synthetic variables", + "termination" : "" + }, + { + "raw" : "!syn", + "sample" : "!syn", + "termination" : "" + }, + { + "raw" : "x1[x2]", + "sample" : "x1[x2]", + "termination" : "" + }, + { + "raw" : "x1()", + "sample" : "x1()", + "termination" : "" + }, + { + "raw" : "x1(x2)", + "sample" : "x1(x2)", + "termination" : "" + }, + { + "raw" : "x1 { x2 }", + "sample" : "x1 { x2 }", + "termination" : "" + }, + { + "raw" : "!{ x1 }{ x2 }", + "sample" : "!{ x1 }{ x2 }", + "termination" : "" + }, + { + "raw" : "!{{{ x1 }}}{{{ x2 }}}", + "sample" : "!{{{ x1 }}}{{{ x2 }}}", + "termination" : "" + }, + { + "raw" : "!!!x1", + "sample" : "!!!x1", + "termination" : "" + }, + { + "raw" : "(-x1 * +x2)", + "sample" : "(-x1 * +x2)", + "termination" : "" + }, + { + "raw" : "x1 { b += x2 } // b", + "sample" : "x1 { true += x2 }", + "termination" : "true" + }, + { + "raw" : "!i{ +(b !in b2 in i2 > s voila i3) }", + "sample" : "!i{ +(b !in b2 in i2 > s voila i3) }", + "termination" : "" + } +] \ No newline at end of file diff --git a/doc/samples/unary/data.json b/doc/samples/unary/data.json new file mode 100644 index 0000000..0a20772 --- /dev/null +++ b/doc/samples/unary/data.json @@ -0,0 +1,52 @@ +[ + { + "raw" : "+s // s", + "sample" : "+\"abc\"", + "termination" : "abc" + }, + { + "raw" : "- s // s", + "sample" : "- \"abcdef\"", + "termination" : "abcdef" + }, + { + "raw" : "!s // s", + "sample" : "!\"qwerty\"", + "termination" : "qwerty" + }, + { + "raw" : "+ s + -b // s b", + "sample" : "+ \"abc\" + -true", + "termination" : "abc, true" + }, + { + "raw" : "s - +b // s b", + "sample" : "\"abc\" - +false", + "termination" : "abc, false" + }, + { + "raw" : "s%!i // s i", + "sample" : "\"abc\"%!13", + "termination" : "abc, 13" + }, + { + "raw" : "+-b // b", + "sample" : "+-false", + "termination" : "false" + }, + { + "raw" : "+-s+-b // s b", + "sample" : "+-\"qwerty\"+-true", + "termination" : "qwerty, true" + }, + { + "raw" : "!s+!!i // s i", + "sample" : "!\"bbb\"+!!29", + "termination" : "bbb, 29" + }, + { + "raw" : "+-+-s // s", + "sample" : "+-+-\"abcdef\"", + "termination" : "abcdef" + } +] \ No newline at end of file diff --git a/doc/targetFunctions.md b/doc/targetFunctions.md new file mode 100644 index 0000000..bf34992 --- /dev/null +++ b/doc/targetFunctions.md @@ -0,0 +1,122 @@ + +# Target functions +Target functions are functions which have been annotated with `@GlobalPattern`/`@LocalPattern`. + +## Restrictions +These types of functions can not be used as target functions: +- anonymous or local functions +- generic functions +- functions with following modifiers: + - inline + - operator + - suspend + - tailrec + +Some restrictions may be revised later. + +### @GlobalPattern target functions +Target functions annotated with `@GlobalPattern` must be: +- top level functions +- with `public` or `internal` visibility + +### @LocalPattern target functions +Target functions annotated with `@LocalPattern` must be: +- member functions of non-abstract top level classes +- with `public` or `internal` visibility + +## Returned type +If a target function returns a value, then the type of whole expression corresponding to the target function's pattern will be the same as return type of the target function. + +```kotlin +// Example-002 + +@GlobalPattern("string * count") +fun repeatString(count: Int, string: String) = buildString { + repeat(count) { append(string) } +} + +fun main() { + println("abc" * 3) // prints "abcabcabc" +} +``` + +## Parameters + +### Extension receivers +You can annotate a target function with an extension receiver. Extension receiver must be provided via one of *receiver providing mechanisms*. + +#### Receiver providing mechanisms +To pass the value for receiver during termination you can: +- use `this` identifier somewhere in a pattern (thus defining a certain place for a receiver object in the expression) +- use a *receiver catcher* + - receiver catcher is unmatched identifier of a pattern that "brings" a receiver value to termination (this keyword will be declared as an extension property on target function's receiver type, so the expression will be available only there where a receiver of required type is available) + - a first unmatched identifier of target function pattern will be used as *receiver catcher* if necessary. If there are no unmatched identifiers in a pattern - add one. + +**Example of receiver catcher usage** +```kotlin +// Example-012 + +class Scope { + val isDebug = true + + fun logMessage(message: String) { + println(message) + } +} + +@GlobalPattern("debug (messageBuilder)") +fun Scope.debugPostponed(messageBuilder: () -> String) { + if (isDebug) logMessage(messageBuilder()) +} + +fun main() { + with(Scope()) { + debug { "message" } + } +} +``` + +**Example of `this` identifier usage** +==examples== + +#### Restrictions +- multiple receivers are not supported +- receivers for `@LocalPattern`-annotated functions are not supported + +### Parameter types (including an extension receiver) + +#### Nullable types +You can use nullable types in your function signature. + +See `Example-011`. + +#### Generic types +Generic parameter types are not fully supported yet. You can use parameters with generic types with type parameters in `invariant` position only. + +#### Functional types +You can use functional types (with optional parameters/receivers) to receive an object of functional type in a target function and use it appropriately (e.g. call its `invoke` under some condition). + +See `Example-011`. + +There can be cases when usage of functional parameters in a pattern makes the pattern erroneous. Pattern "foo bar" in the following example can not be recognized as valid Kotlin statement: + +```kotlin +@GlobalPattern("foo bar") +fun function(bar: () -> Unit) { + bar() +} +``` + +Current workaround for this is to surround functional parameter name in a pattern with braces: + +```kotlin +@GlobalPattern("foo (bar)") +fun function(bar: () -> Unit) { + bar() +} +``` + +### Arbitrary order of target function parameters +Target function parameters can be placed in any order regardless of order used in a pattern. Exception is when some of `OperatorInfo` parameters are present in a function signature, then the order of function parameters must match those in the pattern. + +See `Example-014`, `Example-002`. diff --git a/doc/unsafe.md b/doc/unsafe.md new file mode 100644 index 0000000..a329679 --- /dev/null +++ b/doc/unsafe.md @@ -0,0 +1,19 @@ + +## Unsafe features +"Unsafe" doesn't mean "poorly implemented", it means that unsafe features can be misused by a DSL user inadvertently. + +Runtime correctness for unsafe features can be guaranteed only in the case when **runtime expression structure is exactly the same as in the pattern**. Otherwise, some required arguments of target function will be undefined by the moment of termination and an exception will be raised. + +### Example of incorrect usage +Given the pattern below +```kotlin +@GlobalPattern("foo { i > s }") +fun bar(i: Int, s: String) {} +``` +the following expression `foo { true }` will be syntactically correct (`i > s` has `Boolean` type) but actual values for `i` and `s` will be unknown. + +### Processor option +All unsafe features are disabled by default, but can be enabled by setting `ksp.io.kabu.allowUnsafe` option to `true`. + +### List of unsafe features +- actual used operator retrieval (comparison/inclusion)