├── .gitignore ├── gradle.properties ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── jsTest │ └── kotlin │ │ └── guru │ │ └── zoroark │ │ └── pangoro │ │ └── Utils.kt ├── commonMain │ └── kotlin │ │ └── guru │ │ └── zoroark │ │ └── pangoro │ │ ├── PangoroNode.kt │ │ ├── dsl │ │ ├── PangoroDsl.kt │ │ ├── PangoroExpectationBuilder.kt │ │ ├── PangoroDslExpectations.kt │ │ ├── PangoroOptionalDsl.kt │ │ ├── PangoroDescriptorBuilder.kt │ │ ├── PangoroParserBuilder.kt │ │ └── PangoroEitherDsl.kt │ │ ├── PangoroException.kt │ │ ├── PangoroParsingContext.kt │ │ ├── PangoroDescribedType.kt │ │ ├── PangoroTypeDescription.kt │ │ ├── PangoroNodeDeclaration.kt │ │ ├── ExpectationResult.kt │ │ ├── expectations │ │ ├── PangoroExpectedOptional.kt │ │ ├── PangoroExpectedLixyToken.kt │ │ ├── PangoroExpectedNode.kt │ │ ├── PangoroExpectedEither.kt │ │ └── PangoroExpectation.kt │ │ ├── ReflectiveNodeDeclaration.kt │ │ └── PangoroParser.kt ├── jvmTest │ └── kotlin │ │ └── guru │ │ └── zoroark │ │ └── pangoro │ │ ├── Utils.kt │ │ └── TestReflectiveNodeDeclaration.kt ├── commonTest │ └── kotlin │ │ └── guru │ │ └── zoroark │ │ └── pangoro │ │ ├── Utils.kt │ │ ├── TypeDescriptionTest.kt │ │ ├── OptionalDslTest.kt │ │ ├── ExpectedNodeTest.kt │ │ ├── ExpectationApplyTest.kt │ │ ├── ParserTest.kt │ │ ├── DslTest.kt │ │ ├── EitherDslTest.kt │ │ └── ExpectedEitherTest.kt ├── jsMain │ └── kotlin │ │ └── guru │ │ └── zoroark │ │ └── pangoro │ │ └── PangoroJs.kt └── jvmMain │ └── kotlin │ └── guru │ └── zoroark │ └── pangoro │ └── PangoroJvm.kt ├── jitpack.yml ├── .github └── workflows │ ├── node.yml │ └── gradle.yml ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .idea -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pangoro' 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utybo/Pangoro/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/jsTest/kotlin/guru/zoroark/pangoro/Utils.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | actual fun String.containsJvm(substring: String): Boolean { 4 | return true 5 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroNode.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * Root interface for all nodes in the Pangoro AST. 5 | */ 6 | interface PangoroNode -------------------------------------------------------------------------------- /src/jvmTest/kotlin/guru/zoroark/pangoro/Utils.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | actual fun String.containsJvm(substring: String): Boolean { 4 | return contains(substring) 5 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroDsl.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | /** 4 | * Marker for the Pangoro DSL. 5 | */ 6 | @DslMarker 7 | @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, 8 | AnnotationTarget.FUNCTION 9 | ) 10 | annotation class PangoroDsl -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | install: 4 | # Exact same command but excluding tests, because this launches JS tests which require having a browser 5 | # on the machine 6 | - chmod +x gradlew 7 | - ./gradlew clean -Pgroup=$GROUP -Pversion=$VERSION build publishToMavenLocal -x allTests -x jvmTest -x jsBrowserTest -x jsNodeTest -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/Utils.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * Runs [String.contains] on the JVM, returns true on JS. 5 | * 6 | * This is because the reflection system is limited on JS and we can't check for 7 | * some things like class qualified name being returned. 8 | */ 9 | expect fun String.containsJvm(substring: String): Boolean -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: JS tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | CC_TEST_REPORTER_ID: 426a8cba7a9651be2761a6f78bd46e622c0c4e2dd7d611e21b57154ff97ba5cc 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 14 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 14 16 | - name: Build 17 | run: chmod +x gradlew && ./gradlew assemble 18 | - name: Launch tests 19 | run: ./gradlew jsTest -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroException.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * Exception type for anything thrown by Pangoro. 5 | * 6 | * Note that individual expectations should not throw Pangoro exceptions when 7 | * they do not match: instead they should return a 8 | * [ExpectationResult.DidNotMatch] object. A `PangoroException` being thrown 9 | * indicates that something is very wrong, for example that the parser is 10 | * not configured properly. 11 | */ 12 | class PangoroException(message: String, cause: Throwable? = null) : 13 | Exception(message, cause) -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroParsingContext.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.LixyToken 4 | 5 | /** 6 | * This object contains the information that is passed to expectations, and is 7 | * global over a single parser run. 8 | * 9 | * @property tokens The list of tokens that should be parsed 10 | * 11 | * @property typeMap A map with all of the known declared types and their 12 | * description 13 | */ 14 | class PangoroParsingContext( 15 | val tokens: List, 16 | val typeMap: Map, PangoroDescribedType> 17 | ) -------------------------------------------------------------------------------- /src/jsMain/kotlin/guru/zoroark/pangoro/PangoroJs.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * This class is not supported on JavaScript. 7 | */ 8 | actual class ReflectiveNodeDeclaration actual constructor( 9 | tClass: KClass 10 | ) : PangoroNodeDeclaration { 11 | init { 12 | error("reflective() and ReflectiveNodeDeclaration are not supported on JS") 13 | } 14 | actual override fun make(args: PangoroTypeDescription): T { 15 | error("reflective() and ReflectiveNodeDeclaration are not supported on JS") 16 | } 17 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroDescribedType.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.pangoro.expectations.PangoroExpectation 4 | 5 | /** 6 | * A node type declaration (providing a way to make the actual type) with its 7 | * descriptor (which declares what the type expects). 8 | * 9 | * @property type The type described 10 | * 11 | * @property expectations The descriptor: currently, just a list of the 12 | * expectations that make up this type 13 | */ 14 | class PangoroDescribedType( 15 | val type: PangoroNodeDeclaration<*>, 16 | val expectations: List 17 | ) -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroTypeDescription.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * The description for the creation of a type 5 | */ 6 | class PangoroTypeDescription( 7 | /** 8 | * The arguments: that is, everything that was expected and stored using 9 | * the `storeIn`/`storeValueIn` constructs. 10 | */ 11 | val arguments: Map 12 | ) { 13 | /** 14 | * Retrieve the given argument, casting it to `T` automatically 15 | */ 16 | inline operator fun get(str: String): T { 17 | val value = arguments.getOrElse(str) { 18 | throw PangoroException("Key '$str' does not exist in the stored arguments") 19 | } 20 | if (value is T) { 21 | return value // Auto-cast by Kotlin 22 | } else { 23 | throw PangoroException("Expected $str to be of type ${T::class}, but it is actually of type ${value::class}") 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroExpectationBuilder.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.Buildable 4 | import guru.zoroark.pangoro.expectations.PangoroExpectation 5 | 6 | /** 7 | * Simple builder for an expectation. The only real use of this class is to add 8 | * the "storeIn" thing on-the-fly-ish, after the call to "expect". 9 | */ 10 | class PangoroExpectationBuilder(private val builderFunc: (String?) -> PangoroExpectation) : Buildable { 11 | private var storeIn: String? = null 12 | 13 | /** 14 | * Signals that the expectation should store its result using the given 15 | * argument name. 16 | */ 17 | infix fun storeIn(argName: String) { 18 | storeIn = argName 19 | } 20 | 21 | /** 22 | * Short notation for [storeIn]. 23 | */ 24 | operator fun rem(argName: String) { 25 | storeIn = argName 26 | } 27 | 28 | /** 29 | * Builds this expectation 30 | */ 31 | override fun build(): PangoroExpectation { 32 | return builderFunc(storeIn) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroNodeDeclaration.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * A *node declaration*. This simple interface provides a way to turn the 5 | * description of a node into an actual node. 6 | * 7 | * You would typically implement it like so: 8 | * 9 | * ``` 10 | * class MyNodeType(val child: MyOtherNodeType) : PangoroNode { 11 | * companion object : PangoroNodeDeclaration { 12 | * override fun make(args: PangoroTypeDescription) = 13 | * MyNodeType(args["child"]) 14 | * } 15 | * } 16 | * ``` 17 | * 18 | * @see make 19 | */ 20 | interface PangoroNodeDeclaration { 21 | /** 22 | * This function creates a node of type [T] from the arguments. 23 | * 24 | * The arguments contain everything that needed to be stored for the 25 | * creation of the node. If you stored something in the descriptor for this 26 | * node, it will be available in [args]. 27 | * 28 | * @args The description of the node, which contains all of the stored 29 | * information for creating it. 30 | */ 31 | fun make(args: PangoroTypeDescription): T 32 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroDslExpectations.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.LixyTokenType 4 | import guru.zoroark.pangoro.PangoroNodeDeclaration 5 | import guru.zoroark.pangoro.expectations.PangoroExpectedLixyToken 6 | import guru.zoroark.pangoro.expectations.PangoroExpectedNode 7 | 8 | /** 9 | * Adds an expectation to this node descriptor based on a token type. 10 | * 11 | * A token of the given token type is expected at this point. 12 | */ 13 | @PangoroDsl 14 | infix fun ExpectationReceiver.expect(tokenType: LixyTokenType): PangoroExpectationBuilder = 15 | PangoroExpectationBuilder { 16 | PangoroExpectedLixyToken(tokenType, it) 17 | }.also { 18 | this += it 19 | } 20 | 21 | /** 22 | * Adds an expectation to this node descriptor based on a node 23 | * 24 | * A chain of tokens that corresponds to the given node is expected at this 25 | * point 26 | */ 27 | @PangoroDsl 28 | infix fun ExpectationReceiver.expect(node: PangoroNodeDeclaration<*>): PangoroExpectationBuilder = 29 | PangoroExpectationBuilder { 30 | PangoroExpectedNode(node, it) 31 | }.also { 32 | this += it 33 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/ExpectationResult.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | /** 4 | * Class for representing the results of an expectation 5 | */ 6 | sealed class ExpectationResult { 7 | /** 8 | * This expectation matched successfully. It stored values in [stored] 9 | * and the next index that needs to be checked would be [nextIndex]. 10 | * 11 | * @property stored Values that were stored as a result of this 12 | * expectation 13 | * 14 | * @property nextIndex The next index that needs to be checked to continue 15 | * the parsing process. May be out of bounds (e.g. to indicate the end of 16 | * the string) 17 | */ 18 | class Success(val stored: Map, val nextIndex: Int) : 19 | ExpectationResult() 20 | 21 | /** 22 | * This expectation did not match. 23 | */ 24 | data class DidNotMatch( 25 | /** 26 | * A human-readable reason for why this failed 27 | */ 28 | val message: String, 29 | /** 30 | * The index of the token the failure happened. **This index may be out 31 | * of bounds.** 32 | */ 33 | val atTokenIndex: Int 34 | ) : ExpectationResult() 35 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/expectations/PangoroExpectedOptional.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.expectations 2 | 3 | import guru.zoroark.pangoro.ExpectationResult 4 | import guru.zoroark.pangoro.PangoroParsingContext 5 | 6 | /** 7 | * An optional branch. This expectation is always met (always returns a 8 | * success), even when we have ran out of tokens. 9 | * 10 | * The returned success has an empty map if we have ran out of tokens (token 11 | * drought) or if the branch does not match. Otherwise, the map is the one 12 | * returned by the branch (so it passes directly all the matched stuff). 13 | */ 14 | class PangoroExpectedOptional(private val expectations: List) : 15 | PangoroExpectation(), HandlesTokenDrought { 16 | override fun matches( 17 | context: PangoroParsingContext, 18 | index: Int 19 | ): ExpectationResult = 20 | if (index >= context.tokens.size) 21 | ExpectationResult.Success(mapOf(), index) 22 | else when (val result = expectations.apply(context, index)) { 23 | is ExpectationResult.Success -> result 24 | is ExpectationResult.DidNotMatch -> ExpectationResult.Success( 25 | mapOf(), 26 | index 27 | ) 28 | } 29 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/TypeDescriptionTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.test.* 4 | 5 | class TypeDescriptionTest { 6 | @Test 7 | fun argument_retrieval_works() { 8 | val ptd = PangoroTypeDescription(mapOf("One" to "Two")) 9 | val str: String = ptd["One"] 10 | assertEquals("Two", str) 11 | } 12 | 13 | @Test 14 | fun argument_retrieval_wrong_type_fails() { 15 | val ptd = PangoroTypeDescription(mapOf("One" to "Two")) 16 | assertFailsWith { 17 | val str: Int = ptd["One"] 18 | println("But $str is not an integer!") // Should not happen 19 | }.apply { 20 | val msg = message 21 | assertNotNull(msg) 22 | assertTrue(msg.contains("type")) 23 | } 24 | } 25 | 26 | @Test 27 | fun argument_retrieval_key_absent_fails() { 28 | val ptd = PangoroTypeDescription(mapOf("One" to "Two")) 29 | assertFailsWith { 30 | val str: String = ptd["Three"] 31 | println("But $str does not exist!") // Should not happen 32 | }.apply { 33 | val msg = message 34 | assertNotNull(msg) 35 | assertTrue(msg.contains("does not exist") && msg.contains("Three")) 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: JVM tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | CC_TEST_REPORTER_ID: 4e8e928655e33bb4bef61818877516b664a05e07cf22bf308bb158d8711fd38d 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 14 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 14 16 | - name: Build 17 | run: chmod +x gradlew && ./gradlew assemble 18 | - name: Set up Code Climate test reporter 19 | run: | 20 | export GIT_COMMIT_SHA=${GITHUB_SHA} 21 | export GIT_BRANCH=${GITHUB_REF#refs/heads/} 22 | wget -O cc-test-reporter https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 23 | chmod +x cc-test-reporter 24 | - name: Notify Code Climate before build 25 | run: | 26 | ./cc-test-reporter before-build 27 | - name: Launch tests 28 | run: ./gradlew jvmTest 29 | - name: Generate test coverage report 30 | run: ./gradlew jacocoTestReport 31 | - name: Upload test coverage to Code Climate 32 | env: 33 | JACOCO_SOURCE_PATH: src/commonMain/kotlin src/jvmMain/kotlin 34 | run: | 35 | ./cc-test-reporter format-coverage build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml --input-type jacoco 36 | ./cc-test-reporter upload-coverage -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/ReflectiveNodeDeclaration.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * Creates a node declaration that uses reflection to initialize instances of 7 | * the nodes. 8 | * 9 | * Intended to be used with delegated implementations: 10 | * 11 | * ``` 12 | * data class MyNode(...) : PangoroNode { 13 | * companion object : PangoroNodeDeclaration by reflective() 14 | * } 15 | * ``` 16 | * 17 | * @see ReflectiveNodeDeclaration 18 | */ 19 | inline fun reflective(): PangoroNodeDeclaration = 20 | ReflectiveNodeDeclaration(T::class) 21 | 22 | /** 23 | * An implementation of [PangoroNodeDeclaration] that uses reflection to 24 | * initialize instances of the [PangoroNode] class. 25 | * 26 | * The arguments of the node class' constructor are filled in using the 27 | * arguments of the [PangoroTypeDescription] passed to the [make] function. 28 | * Keys are mapped to the constructors' argument parameter names, and the first 29 | * match is used to initialize the class. 30 | * 31 | * This class supports classes with multiple constructors: just remember that 32 | * constructors are chosen exclusively based on parameter names, not typing. 33 | * 34 | * Unavailable on JS. 35 | */ 36 | expect class ReflectiveNodeDeclaration(tClass: KClass) : 37 | PangoroNodeDeclaration { 38 | override fun make(args: PangoroTypeDescription): T 39 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/expectations/PangoroExpectedLixyToken.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.expectations 2 | 3 | import guru.zoroark.lixy.LixyTokenType 4 | import guru.zoroark.pangoro.ExpectationResult 5 | import guru.zoroark.pangoro.PangoroParsingContext 6 | 7 | /** 8 | * An expectation that expects a token to be present at this point. 9 | * 10 | * For example: 11 | * 12 | * ``` 13 | * "Hello!" 14 | * Tokens: [QUOTE = ", STRING_CONTENT = Hello!, QUOTE = "] 15 | * ``` 16 | * 17 | * You could define a "string value" node with three expectations: 18 | * 19 | * - For the first `"`, an expectation for a QUOTE token (see 20 | * [PangoroExpectedLixyToken]) 21 | * 22 | * - For the string content in the middle, an expectation for a STRING_CONTENT 23 | * token. 24 | * 25 | * - For the second `"`, an expectation for a QUOTE token. 26 | */ 27 | class PangoroExpectedLixyToken( 28 | private val tokenType: LixyTokenType, 29 | storeValueIn: String? = null 30 | ) : PangoroExpectation(storeValueIn) { 31 | override fun matches( 32 | context: PangoroParsingContext, 33 | index: Int 34 | ): ExpectationResult = with(context) { 35 | val token = tokens[index] 36 | if (token.tokenType == tokenType) 37 | return ExpectationResult.Success( 38 | if (storeValueIn == null) mapOf() 39 | else mapOf(storeValueIn to token.string), 40 | index + 1 41 | ) 42 | return ExpectationResult.DidNotMatch( 43 | "Expected token of type $tokenType, but encountered ${token.tokenType} instead", 44 | index 45 | ) 46 | } 47 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/OptionalDslTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.test.* 4 | import guru.zoroark.lixy.* 5 | import guru.zoroark.lixy.matchers.repeated 6 | import guru.zoroark.pangoro.dsl.expect 7 | import guru.zoroark.pangoro.dsl.optional 8 | import guru.zoroark.pangoro.dsl.pangoro 9 | 10 | 11 | class OptionalDslTest { 12 | 13 | data class Decimal(val intPart: String, val decPart: String? = null) : 14 | PangoroNode { 15 | companion object : PangoroNodeDeclaration { 16 | override fun make(args: PangoroTypeDescription) = 17 | if (args.arguments.containsKey("decPart")) 18 | Decimal(args["intPart"], args["decPart"]) 19 | else 20 | Decimal(args["intPart"]) 21 | } 22 | } 23 | 24 | @Test 25 | fun test_optional_dsl() { 26 | val tNum = tokenType() 27 | val tDot = tokenType() 28 | val lexer = lixy { 29 | state { 30 | ('0'..'9').repeated isToken tNum 31 | "." isToken tDot 32 | } 33 | } 34 | val parser = pangoro { 35 | Decimal root { 36 | expect(tNum) storeIn "intPart" 37 | optional { 38 | expect(tDot) 39 | expect(tNum) storeIn "decPart" 40 | } 41 | } 42 | } 43 | val result = parser.parse(lexer.tokenize("123.456")) 44 | assertEquals(Decimal("123", "456"), result) 45 | 46 | val result2 = parser.parse(lexer.tokenize("789")) 47 | assertEquals(Decimal("789"), result2) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroOptionalDsl.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.Buildable 4 | import guru.zoroark.pangoro.expectations.PangoroExpectation 5 | import guru.zoroark.pangoro.expectations.PangoroExpectedOptional 6 | 7 | /** 8 | * Create an optional branch. If the optional branch matches, what it stores 9 | * will is passed transparently, just like if the `optional { }` was not there. 10 | * If the optional branch does not match, nothing happens, just like if the 11 | * entire optional branch was not there. 12 | * 13 | * Typical usage may look like: 14 | * 15 | * ``` 16 | * MyNode { 17 | * // Everything here must be present in order to have a match... 18 | * expect(...) 19 | * expect(...) 20 | * optional { 21 | * // This branch is optional 22 | * expect(...) 23 | * } 24 | * } 25 | */ 26 | @PangoroDsl 27 | fun ExpectationReceiver.optional(optionalBlock: OptionalBranchBuilder.() -> Unit) { 28 | this += OptionalBranchBuilder().apply(optionalBlock) 29 | } 30 | 31 | /** 32 | * Builder class for an optional expectation. 33 | */ 34 | class OptionalBranchBuilder : 35 | ExpectationReceiver, Buildable { 36 | private val expectations = mutableListOf>() 37 | 38 | override fun plusAssign(expectationBuilder: Buildable) { 39 | expectations += expectationBuilder 40 | } 41 | 42 | /** 43 | * Build this expected optional 44 | */ 45 | override fun build(): PangoroExpectation { 46 | return PangoroExpectedOptional(expectations.map { it.build() }) 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/ExpectedNodeTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.pangoro.expectations.PangoroExpectedNode 4 | import kotlin.test.* 5 | 6 | class ExpectedNodeTest { 7 | class One : PangoroNode { 8 | companion object : PangoroNodeDeclaration { 9 | override fun make(args: PangoroTypeDescription): One = One() 10 | } 11 | } 12 | 13 | class Two : PangoroNode { 14 | companion object : PangoroNodeDeclaration { 15 | override fun make(args: PangoroTypeDescription): Two = Two() 16 | } 17 | } 18 | 19 | @Test 20 | fun test_undefined_expected_node_fails() { 21 | val exp = PangoroExpectedNode(Two, null) 22 | assertFailsWith { 23 | exp.matches( 24 | PangoroParsingContext( 25 | listOf(), 26 | mapOf(One to PangoroDescribedType(One, listOf())) 27 | ), 28 | 0 29 | ) 30 | }.apply { 31 | val msg = message 32 | assertNotNull(msg) 33 | assertTrue( 34 | msg.containsJvm("Two") && msg.contains("declared"), 35 | "Expected 'Two' and 'declared' in exception message $msg" 36 | ) 37 | } 38 | } 39 | 40 | @Test 41 | fun test_successful() { 42 | val exp = PangoroExpectedNode(Two, "yeet") 43 | val res = exp.matches( 44 | PangoroParsingContext( 45 | listOf(), 46 | mapOf(Two to PangoroDescribedType(Two, listOf())) 47 | ), 0 48 | ) 49 | assertTrue(res is ExpectationResult.Success) 50 | assertTrue(res.stored["yeet"] is Two) 51 | } 52 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/expectations/PangoroExpectedNode.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.expectations 2 | 3 | import guru.zoroark.pangoro.* 4 | import guru.zoroark.pangoro.ExpectationResult.DidNotMatch 5 | import guru.zoroark.pangoro.ExpectationResult.Success 6 | 7 | /** 8 | * An expectation that expects another node to be present at this point. 9 | * 10 | * For example: 11 | * 12 | * ``` 13 | * x = (1 + 2) + 3 14 | * ``` 15 | * 16 | * You could define an "assignment" node with three expectations: 17 | * 18 | * - For the first operand and the equals sign, a simple 19 | * [PangoroExpectedLixyToken] 20 | * 21 | * - For the right operand, a `PangoroExpectedNode(ExpressionNode)`. 22 | * 23 | * This expectation is the main way of composing nodes. 24 | */ 25 | class PangoroExpectedNode( 26 | private val node: PangoroNodeDeclaration<*>, 27 | storeValueIn: String? = null 28 | ) : PangoroExpectation(storeValueIn) { 29 | override fun matches( 30 | context: PangoroParsingContext, 31 | index: Int 32 | ): ExpectationResult = context.typeMap[node]?.let { describedNode -> 33 | when (val result = describedNode.expectations.apply(context, index)) { 34 | is Success -> 35 | Success(storeValueIn 36 | ?.mappedWith( 37 | describedNode.type.make( 38 | PangoroTypeDescription(result.stored) 39 | ) 40 | ) 41 | ?: mapOf(), result.nextIndex 42 | ) 43 | is DidNotMatch -> result 44 | } 45 | } ?: throw PangoroException( 46 | "Node ${node::class} is expected but not declared in the parser" 47 | ) 48 | } 49 | 50 | private fun T1.mappedWith(x: T2): Map = mapOf(this to x) -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/PangoroParser.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.LixyToken 4 | import guru.zoroark.pangoro.expectations.PangoroExpectation 5 | import guru.zoroark.pangoro.expectations.PangoroExpectedNode 6 | 7 | /** 8 | * Class for a full parser that is ready to parse a chain of tokens. 9 | * 10 | * @param R The type of the *root node*, the node at the root of the abstract 11 | * syntax tree 12 | * 13 | * @param types List of described types that will be used for the parsing 14 | * process. Each entry describes a type of node that can be encountered in the 15 | * AST. 16 | * 17 | * @param rootType The expected type of the root node. 18 | */ 19 | class PangoroParser( 20 | types: List, 21 | private val rootType: PangoroNodeDeclaration 22 | ) { 23 | private val rootExpectation: PangoroExpectation = 24 | PangoroExpectedNode(rootType, "root") 25 | 26 | private val typeMap: Map, PangoroDescribedType> = 27 | types.associateBy { it.type } 28 | 29 | /** 30 | * Launch the parser on the given tokens. 31 | */ 32 | fun parse(lixyTokens: List): R { 33 | val result = rootExpectation.matches( 34 | PangoroParsingContext(lixyTokens, typeMap), 35 | 0 36 | ) 37 | // TODO throw exception when result index != end of tokens 38 | // (i.e. we didn't parse everything) 39 | @Suppress("UNCHECKED_CAST") 40 | return when (result) { 41 | is ExpectationResult.DidNotMatch -> 42 | throw PangoroException("Parsing failed: ${result.message} (token nb ${result.atTokenIndex})") 43 | is ExpectationResult.Success -> 44 | result.stored["root"] as? R 45 | ?: error("Internal Pangoro error: the root result was not stored. Please report this.") 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/expectations/PangoroExpectedEither.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.expectations 2 | 3 | import guru.zoroark.pangoro.ExpectationResult 4 | import guru.zoroark.pangoro.PangoroParsingContext 5 | 6 | /** 7 | * An expectation that takes the result of some [branches][PangoroEitherBranch]. 8 | * 9 | * The expectation returns the result of the first successful branch, or a 10 | * DidNotMatch with a message that contains all of the DidNotMatch messages of 11 | * the branches. 12 | * 13 | * Branches are checked in the list's order. 14 | * 15 | * @property branches The branches of this expectation. They are always 16 | * checked in the list's order. 17 | */ 18 | class PangoroExpectedEither(private val branches: List) : 19 | PangoroExpectation() { 20 | override fun matches( 21 | context: PangoroParsingContext, 22 | index: Int 23 | ): ExpectationResult { 24 | val failures = mutableListOf() 25 | branches.forEach { 26 | when (val result = it.expectations.apply(context, index)) { 27 | is ExpectationResult.Success -> { 28 | return result 29 | } 30 | is ExpectationResult.DidNotMatch -> { 31 | failures += result 32 | } 33 | } 34 | } 35 | return ExpectationResult.DidNotMatch( 36 | "None of the ${branches.size} branches matched.\n" + 37 | failures.mapIndexed { i, x -> 38 | " -> Branch $i:\n${x.message.prependIndent(" ")}\n" 39 | }, 40 | index 41 | ) 42 | } 43 | } 44 | 45 | /** 46 | * A branch for the [PangoroExpectedEither] expectation. 47 | * 48 | * @property expectations The expectations that are a part of this branch. 49 | */ 50 | class PangoroEitherBranch(val expectations: List) -------------------------------------------------------------------------------- /src/jvmMain/kotlin/guru/zoroark/pangoro/PangoroJvm.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.KFunction 5 | import kotlin.reflect.KParameter 6 | import kotlin.reflect.full.isSubtypeOf 7 | import kotlin.reflect.full.starProjectedType 8 | 9 | /** 10 | * An implementation of [PangoroNodeDeclaration] that uses reflection to 11 | * initialize instances of the [PangoroNode] class. 12 | * 13 | * The arguments of the node class' constructor are filled in using the 14 | * arguments of the [PangoroTypeDescription] passed to the [make] function. 15 | * Keys are mapped to the constructors' argument parameter names, and the first 16 | * match is used to initialize the class. 17 | * 18 | * This class supports classes with multiple constructors: just remember that 19 | * constructors are chosen exclusively based on parameter names, not typing. 20 | */ 21 | actual class ReflectiveNodeDeclaration actual constructor( 22 | private val tClass: KClass 23 | ) : 24 | PangoroNodeDeclaration { 25 | actual override fun make(args: PangoroTypeDescription): T { 26 | val ctor = findValidConstructor(args.arguments) 27 | // TODO Better error messages on reflection errors 28 | val callArgs = 29 | ctor.parameters 30 | .filter { args.arguments.containsKey(it.name) } 31 | .associateWith { args.arguments[it.name]!! } 32 | return ctor.callBy(callArgs) 33 | } 34 | 35 | private fun findValidConstructor(arguments: Map): KFunction { 36 | return tClass.constructors.filter { 37 | // All parameters have a value in the map (except for optional parameters) 38 | // with a compatible type 39 | it.parameters.all { param -> 40 | val matchingArg = 41 | arguments[param.name] ?: return@all param.isOptional 42 | param.isCompatibleWith(matchingArg) 43 | } 44 | }.maxBy { 45 | // Pick the constructor with the most non-optional parameters 46 | it.parameters.filter { param -> !param.isOptional }.count() 47 | } ?: throw PangoroException( 48 | "Could not find a constructor that uses keys " + 49 | arguments.entries.joinToString(", ") { (k, v) -> 50 | "$k{${v::class.qualifiedName}}" 51 | } 52 | ) 53 | } 54 | } 55 | 56 | private fun KParameter.isCompatibleWith(matchingArg: Any): Boolean = 57 | matchingArg::class.starProjectedType.isSubtypeOf(type) -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroDescriptorBuilder.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.Buildable 4 | import guru.zoroark.lixy.LixyTokenType 5 | import guru.zoroark.lixy.selfBuildable 6 | import guru.zoroark.pangoro.PangoroDescribedType 7 | import guru.zoroark.pangoro.expectations.PangoroExpectation 8 | import guru.zoroark.pangoro.PangoroNode 9 | import guru.zoroark.pangoro.PangoroNodeDeclaration 10 | 11 | /** 12 | * Builder for the description of a node. This typically should be used like 13 | * this: 14 | * 15 | * ``` 16 | * MyNodeType { 17 | * expect(TokenType) 18 | * expect(MyOtherNodeType) 19 | * } 20 | * ``` 21 | */ 22 | @PangoroDsl 23 | class PangoroDescriptorBuilder( 24 | private val typeDeclaration: PangoroNodeDeclaration 25 | ) : ExpectationReceiver, Buildable { 26 | /** 27 | * List of the expectations that should be built. 28 | */ 29 | private val expectations = mutableListOf>() 30 | 31 | /** 32 | * Add an expectation that will be built when the description gets built. 33 | */ 34 | override operator fun plusAssign(expectationBuilder: Buildable) { 35 | expectations += expectationBuilder 36 | } 37 | 38 | /** 39 | * Builds this as a described type 40 | */ 41 | override fun build(): PangoroDescribedType { 42 | return PangoroDescribedType( 43 | typeDeclaration, 44 | expectations.map { it.build() }) 45 | } 46 | } 47 | 48 | /** 49 | * An expectation receiver is the receiver type for all `expect` DSL constructs. 50 | * Use this if you want your own DSL to be able to have `expect` called on it. 51 | */ 52 | @PangoroDsl 53 | interface ExpectationReceiver { 54 | /** 55 | * Add a buildable expectation to this receiver -- the exact meaning of this 56 | * depends on the implementation 57 | */ 58 | operator fun plusAssign(expectationBuilder: Buildable) 59 | 60 | /** 61 | * Concise notation for the [expect] function. 62 | */ 63 | operator fun LixyTokenType.unaryPlus() = 64 | expect(this) 65 | 66 | /** 67 | * Concise notation for the [expect] function. 68 | */ 69 | operator fun PangoroNodeDeclaration<*>.unaryPlus() = 70 | expect(this) 71 | } 72 | 73 | /** 74 | * Add an expectation directly instead of a builder. This is a shortcut for 75 | * `this += expectation.selfBuildable()` 76 | */ 77 | operator fun ExpectationReceiver.plusAssign(expectation: PangoroExpectation) { 78 | this += expectation.selfBuildable() 79 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroParserBuilder.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.Buildable 4 | import guru.zoroark.pangoro.* 5 | 6 | /** 7 | * DSL builder for a Pangoro parser. 8 | */ 9 | @PangoroDsl 10 | class PangoroParserBuilder : Buildable> { 11 | private var rootNodeType: PangoroNodeDeclaration<*>? = null 12 | private val builtTypeDef = mutableListOf() 13 | private val knownDeclarations = mutableSetOf>() 14 | 15 | /** 16 | * Creates a node declaration for the given node type with the given block 17 | * as its descriptor. 18 | */ 19 | operator fun PangoroNodeDeclaration.invoke(block: PangoroDescriptorBuilder.() -> Unit) { 20 | if (this in knownDeclarations) { 21 | throw PangoroException("The node declaration ${this::class} was already described elsewhere: you cannot describe it twice.") 22 | } 23 | builtTypeDef += PangoroDescriptorBuilder(this).apply(block).build() 24 | knownDeclarations += this 25 | } 26 | 27 | /** 28 | * Similar to [invoke], but also signals that this node is the *root node* 29 | * of the constructed tree. 30 | */ 31 | @PangoroDsl 32 | infix fun PangoroNodeDeclaration.root(block: PangoroDescriptorBuilder.() -> Unit) { 33 | if (rootNodeType != null) { 34 | throw PangoroException("Another node was already defined as the root, ${this::class} cannot also be a root.") 35 | } 36 | this(block) 37 | rootNodeType = this 38 | } 39 | 40 | /** 41 | * Build this parser 42 | */ 43 | override fun build(): PangoroParser<*> = 44 | // user to configure the root node. 45 | PangoroParser( 46 | builtTypeDef, 47 | rootNodeType 48 | ?: throw PangoroException("You never defined a root node: please described a node with 'root' so the parser knows where to start!") 49 | ) 50 | } 51 | 52 | /** 53 | * Main entry-point for the Pangoro DSL. A parser might typically look like this 54 | * 55 | * ``` 56 | * val parser = pangoro { 57 | * MyNodeType { 58 | * expect(tokenType) 59 | * expect(otherTokenType) storeIn "hello" 60 | * } 61 | * 62 | * MyOtherNodeType root { 63 | * expect(someToken) 64 | * expect(MyNodeType) storeIn "theNode" 65 | * } 66 | * } 67 | * parser.parse(tokens) 68 | * ``` 69 | */ 70 | @PangoroDsl 71 | fun pangoro(block: PangoroParserBuilder.() -> Unit): PangoroParser<*> = 72 | PangoroParserBuilder().apply(block).build() -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/ExpectationApplyTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.lixy 4 | import guru.zoroark.lixy.tokenType 5 | import guru.zoroark.pangoro.expectations.PangoroExpectation 6 | import guru.zoroark.pangoro.expectations.PangoroExpectedLixyToken 7 | import guru.zoroark.pangoro.expectations.apply 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertTrue 11 | 12 | class ExpectationApplyTest { 13 | @Test 14 | fun test_successful_with_no_expectations() { 15 | val result = listOf().apply( 16 | PangoroParsingContext(listOf(), mapOf()) 17 | ) 18 | assertTrue(result is ExpectationResult.Success) 19 | assertTrue(result.stored.isEmpty()) 20 | assertEquals(0, result.nextIndex) 21 | } 22 | 23 | @Test 24 | fun test_successful_with_some_expectations_and_storage() { 25 | val one = tokenType() 26 | val two = tokenType() 27 | val exp = listOf( 28 | PangoroExpectedLixyToken(one, "1"), 29 | PangoroExpectedLixyToken(one, "2"), 30 | PangoroExpectedLixyToken(two, "3"), 31 | PangoroExpectedLixyToken(one), 32 | PangoroExpectedLixyToken(two, "4") 33 | ) 34 | val tokens = lixy { 35 | state { 36 | 'a'..'c' isToken one 37 | 'd'..'f' isToken two 38 | } 39 | }.tokenize("abdce") 40 | val result = exp.apply(PangoroParsingContext(tokens, mapOf()), 0) 41 | assertTrue(result is ExpectationResult.Success) 42 | assertEquals( 43 | mapOf("1" to "a", "2" to "b", "3" to "d", "4" to "e"), 44 | result.stored 45 | ) 46 | } 47 | 48 | @Test 49 | fun test_unsuccessful_out_of_tokens() { 50 | val one = tokenType() 51 | val two = tokenType() 52 | val exp = listOf( 53 | PangoroExpectedLixyToken(one, "1"), 54 | PangoroExpectedLixyToken(one, "2"), 55 | PangoroExpectedLixyToken(two, "3"), 56 | PangoroExpectedLixyToken(one), 57 | PangoroExpectedLixyToken(two, "4") 58 | ) 59 | val tokens = lixy { 60 | state { 61 | 'a'..'c' isToken one 62 | 'd'..'f' isToken two 63 | } 64 | }.tokenize("abdc") 65 | val result = exp.apply(PangoroParsingContext(tokens, mapOf())) 66 | assertTrue(result is ExpectationResult.DidNotMatch) 67 | assertEquals(result.atTokenIndex, 4) 68 | } 69 | 70 | @Test 71 | fun test_unsuccessful_no_match() { 72 | val one = tokenType() 73 | val two = tokenType() 74 | val exp = listOf( 75 | PangoroExpectedLixyToken(one, "1"), 76 | PangoroExpectedLixyToken(one, "2"), 77 | PangoroExpectedLixyToken(two, "3"), 78 | PangoroExpectedLixyToken(one), 79 | PangoroExpectedLixyToken(two, "4") 80 | ) 81 | val tokens = lixy { 82 | state { 83 | 'a'..'c' isToken one 84 | 'd'..'f' isToken two 85 | } 86 | }.tokenize("abdfe") 87 | val result = exp.apply(PangoroParsingContext(tokens, mapOf()), 0) 88 | assertTrue(result is ExpectationResult.DidNotMatch) 89 | } 90 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heads up! 2 | 3 | **Pangoro and Lixy have been replaced by [Tegal Niwen](https://tegral.zoroark.guru/docs/modules/core/niwen) ([GitHub](https://github.com/utybo/Tegral)).** This evolved version of Pangoro and Lixy feature parser type-safety, more expectations and matchers, an awesome execution debugger and much more! This repository will no longer be updated. 4 | 5 | ## Previous README 6 | 7 | # [![Pangoro](https://zoroark.guru/img/pangoro.png)](http://pokemondb.net/pokedex/pangoro) Pangoro, the parser with a nice Kotlin DSL 8 | 9 | [![Actions Status JVM](https://img.shields.io/github/workflow/status/utybo/Pangoro/JVM%20tests?style=for-the-badge&logo=github&label=tests%20(JVM))](https://github.com/utybo/Pangoro/actions) 10 | [![Actions Status JS](https://img.shields.io/github/workflow/status/utybo/Pangoro/JS%20tests?style=for-the-badge&logo=github&label=tests%20(JS))](https://github.com/utybo/Pangoro/actions) 11 | [![Code Climate coverage](https://img.shields.io/codeclimate/coverage/utybo/Pangoro?style=for-the-badge&logo=Code-Climate)](https://codeclimate.com/github/utybo/Pangoro/test_coverage) 12 | [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/utybo/Pangoro?style=for-the-badge&logo=Code-Climate)](https://codeclimate.com/github/utybo/Pangoro/maintainability) 13 | ![Made with Kotlin](https://img.shields.io/badge/Made%20with-Kotlin-blue?logo=Kotlin&style=for-the-badge) 14 | 15 | ![Experimental](https://img.shields.io/badge/Stage-Experimental-red?style=flat-square) [![Release](https://jitpack.io/v/guru.zoroark/pangoro.svg?style=flat-square)](https://jitpack.io/#guru.zoroark/pangoro) Pangoro is under active development. 16 | 17 | [**>>> Documentation <<<**](https://docs.zoroark.guru/#/pangoro/) 18 | 19 | Pangoro is a parser intended for use with the [Lixy](https://github.com/utybo/Lixy) lexer, although it can also work with anything, provided that you convert your tokens into Lixy tokens. 20 | 21 | ```kotlin 22 | // Types used by the parser (PangoroNode) and lexer (LixyTokenType) 23 | data class Number(val value: String) : PangoroNode { 24 | companion object : PangoroNodeDeclaration by reflective() 25 | } 26 | 27 | data class Sum(val first: Number, val second: Number) : PangoroNode { 28 | companion object : PangoroNodeDeclaration by reflective() 29 | } 30 | 31 | enum class Tokens : LinkTokenType { 32 | Number, Plus 33 | } 34 | 35 | // Lexer (from Lixy) 36 | val lexer = lixy { 37 | state { 38 | matches("\\d+") isToken Tokens.Number 39 | "+" isToken Tokens.Plus 40 | " ".ignore 41 | } 42 | } 43 | 44 | // Parser (from Pangoro) 45 | val parser = pangoro { 46 | Number { 47 | expect(Tokens.Number) storeIn "value" 48 | } 49 | Sum root { 50 | expect(Number) storeIn "first" 51 | expect(Tokens.Plus) 52 | expect(Number) storeIn "second" 53 | } 54 | } 55 | val tokens = lexer.tokenize("123 + 4567") 56 | val ast: Sum = parser.parse(tokens) 57 | /* 58 | * ast = Sum( 59 | * first = Number(value = "123"), 60 | second = Number(value = "4567") 61 | * ) 62 | */ 63 | ``` 64 | 65 | ## Getting Pangoro 66 | 67 | You can get the following artifacts from Jitpack: 68 | 69 | * Kotlin/JVM: `guru.zoroark.pangoro:pangoro-jvm:version` 70 | * Kotlin/JS: `guru.zoroark.pangoro:pangoro-js:version` 71 | * Kotlin MPP: `guru.zoroark.pangoro:pangoro:version` 72 | 73 | [![Zoroark](https://img.pokemondb.net/sprites/black-white/anim/normal/zoroark.gif)](https://zoroark.guru) 74 | -------------------------------------------------------------------------------- /src/jvmTest/kotlin/guru/zoroark/pangoro/TestReflectiveNodeDeclaration.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import kotlin.test.* 4 | 5 | class TestReflectiveNodeDeclaration { 6 | data class SingleConstructor(val hello: String, val goodbye: Int) : 7 | PangoroNode { 8 | companion object : 9 | PangoroNodeDeclaration by reflective() 10 | } 11 | 12 | @Test 13 | fun reflective_node_declaration_works_single_ctor() { 14 | val sc = SingleConstructor.make( 15 | PangoroTypeDescription(mapOf("hello" to "HI", "goodbye" to 42)) 16 | ) 17 | assertEquals("HI", sc.hello) 18 | assertEquals(42, sc.goodbye) 19 | } 20 | 21 | data class DoubleConstructor(val hello: String, val goodbye: Int) : 22 | PangoroNode { 23 | companion object : 24 | PangoroNodeDeclaration by reflective() 25 | 26 | @Suppress("unused") 27 | constructor(ciao: Int, buongiorno: String) : this(buongiorno, ciao) 28 | } 29 | 30 | @Test 31 | fun reflective_node_declaration_works_double_ctor() { 32 | val sc = DoubleConstructor.make( 33 | PangoroTypeDescription( 34 | mapOf( 35 | "buongiorno" to "ARRIVEDERCI", 36 | "ciao" to 765 37 | ) 38 | ) 39 | ) 40 | assertEquals("ARRIVEDERCI", sc.hello) 41 | assertEquals(765, sc.goodbye) 42 | } 43 | 44 | @Test 45 | fun reflective_node_declaration_fails_on_no_match() { 46 | assertFailsWith { 47 | DoubleConstructor.make( 48 | PangoroTypeDescription( 49 | mapOf("buongiorno" to "ARRIVEDERCI", "goodbye" to 42) 50 | ) 51 | ) 52 | }.apply { 53 | val msg = message 54 | assertNotNull(msg) 55 | assertTrue(msg.contains("Could not find")) 56 | } 57 | } 58 | 59 | data class OptionalConstructor( 60 | val first: String, val e: Char, val optSecond: Int = 42 61 | ) : PangoroNode { 62 | companion object : 63 | PangoroNodeDeclaration by reflective() 64 | } 65 | 66 | @Test 67 | fun reflective_node_declaration_with_optional_params() { 68 | val result = OptionalConstructor.make( 69 | PangoroTypeDescription( 70 | mapOf( 71 | "first" to "Hello", 72 | "e" to 'E' 73 | ) 74 | ) 75 | ) 76 | assertEquals(OptionalConstructor("Hello", 'E'), result) 77 | } 78 | 79 | data class MultiOptionalConstructor( 80 | val first: String, val second: Char, val third: List = listOf() 81 | ) : PangoroNode { 82 | constructor( 83 | second: Char, 84 | first: String = "Heyy", 85 | third: List = listOf() 86 | ) : this("Second ctor $first", second, listOf("Hello")) 87 | 88 | companion object : 89 | PangoroNodeDeclaration by reflective() 90 | } 91 | 92 | @Test 93 | fun reflective_node_declaration_prefer_least_optional() { 94 | val result = MultiOptionalConstructor.make( 95 | PangoroTypeDescription( 96 | mapOf( 97 | "first" to "HELLO", 98 | "second" to 'X' 99 | ) 100 | ) 101 | ) 102 | assertEquals(MultiOptionalConstructor("HELLO", 'X', listOf()), result) 103 | } 104 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/dsl/PangoroEitherDsl.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.dsl 2 | 3 | import guru.zoroark.lixy.Buildable 4 | import guru.zoroark.pangoro.expectations.PangoroEitherBranch 5 | import guru.zoroark.pangoro.expectations.PangoroExpectation 6 | import guru.zoroark.pangoro.expectations.PangoroExpectedEither 7 | 8 | /** 9 | * Build an "either" construct, with different branches the parsing process can 10 | * take. 11 | * 12 | * The branches are checked in the order they are declared. 13 | * 14 | * Typical use may look like: 15 | * 16 | * ``` 17 | * MyNode { 18 | * expect(...) 19 | * either { 20 | * // Expectations for branch one... 21 | * expect(tokenX1) 22 | * expect(OtherNode) 23 | * } or { 24 | * // Expectations for branch two... 25 | * expect(SomethingElse) 26 | * expect(tokenY2) storeIn "hello" 27 | * } or { 28 | * // Expectations for branch three 29 | * expect(EvenMoreDifferent) 30 | * } 31 | * expect(...) 32 | * } 33 | * ``` 34 | * 35 | * Anything stored within a branch will be available for the constructed node, 36 | * provided that the branch was executed. 37 | * 38 | * Note: The lambda receives a builder for a single branch, but this function 39 | * returns the builder for the entire either construct, meaning that: 40 | * 41 | * - You cannot add branches from a branch 42 | * 43 | * - The recommended way to add branches is to call [or], like in the example 44 | * above. 45 | */ 46 | @PangoroDsl 47 | fun ExpectationReceiver.either(branchBuilder: PangoroEitherBranchBuilder.() -> Unit): PangoroEitherBuilder { 48 | val builder = PangoroEitherBuilder() 49 | builder.addBranch(branchBuilder) 50 | this += builder 51 | return builder 52 | } 53 | 54 | /** 55 | * The builder for a single branch. This is the receiver type of every [either] 56 | * branch. 57 | */ 58 | @PangoroDsl 59 | class PangoroEitherBranchBuilder : ExpectationReceiver, 60 | Buildable { 61 | private val expectations = mutableListOf>() 62 | 63 | override fun plusAssign(expectationBuilder: Buildable) { 64 | expectations += expectationBuilder 65 | } 66 | 67 | /** 68 | * Builds this branch 69 | */ 70 | override fun build(): PangoroEitherBranch { 71 | return PangoroEitherBranch(expectations.map { it.build() }) 72 | } 73 | } 74 | 75 | /** 76 | * The builder for the entire either construct. This is different from a single 77 | * branch. 78 | */ 79 | @PangoroDsl 80 | class PangoroEitherBuilder : Buildable { 81 | private val branches = mutableListOf>() 82 | 83 | /** 84 | * Build this either expectation 85 | */ 86 | override fun build(): PangoroExpectedEither { 87 | return PangoroExpectedEither(branches.map { it.build() }) 88 | } 89 | 90 | /** 91 | * Add a branch builder to this either builder 92 | */ 93 | fun addBranch(branch: Buildable) { 94 | branches += branch 95 | } 96 | } 97 | 98 | /** 99 | * Add a branch to this builder. The branch is first initialized through the 100 | * [branchInit] argument. 101 | */ 102 | inline fun PangoroEitherBuilder.addBranch(branchInit: PangoroEitherBranchBuilder.() -> Unit) { 103 | addBranch(PangoroEitherBranchBuilder().apply(branchInit)) 104 | } 105 | 106 | /** 107 | * Adds a branch to the `either` construct. `or` can be used multiple times to get more than two branches, like so: 108 | * 109 | * ``` 110 | * either { 111 | * // ... 112 | * } or { 113 | * // ... 114 | * } or { 115 | * // ... 116 | * } 117 | * ``` 118 | */ 119 | @PangoroDsl 120 | inline infix fun PangoroEitherBuilder.or(branchInit: PangoroEitherBranchBuilder.() -> Unit): PangoroEitherBuilder { 121 | addBranch(branchInit) 122 | return this 123 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/guru/zoroark/pangoro/expectations/PangoroExpectation.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro.expectations 2 | 3 | import guru.zoroark.pangoro.ExpectationResult 4 | import guru.zoroark.pangoro.PangoroParsingContext 5 | import guru.zoroark.pangoro.PangoroTypeDescription 6 | import guru.zoroark.pangoro.PangoroNodeDeclaration 7 | 8 | /** 9 | * General class for an expectation. 10 | * 11 | * Expectations are the core of the parsing algorithms used by Pangoro, and a 12 | * way to express what we *expect* the chain of tokens to look like in order to 13 | * be parsed into an abstract syntax tree. 14 | * 15 | * Each node of the tree is described as a list of expectations over a chain of 16 | * token. In other words, constructing a node is as simple as just checking if 17 | * the list of expectations matches the chain of tokens. 18 | * 19 | * Recognizing tokens is not just about expectations: we also need to store the 20 | * matched value of some expectations. Take a string for example: 21 | * 22 | * ``` 23 | * "Hello World!" 24 | * ``` 25 | * 26 | * While the quotation marks (`"`) are not useful for the abstract syntax tree 27 | * (the fact that this is a string will be represented by having a node type 28 | * specific to string values), the content of the string is very important. 29 | * Therefore, expectations also know where their matched value should be stored 30 | * through the [storeValueIn] property. This stored value can then be retrieved 31 | * through the [PangoroTypeDescription]'s 32 | * [arguments][PangoroTypeDescription.arguments] and are passed using the [make 33 | * function][PangoroNodeDeclaration.make] in the [PangoroNodeDeclaration]. 34 | * 35 | * If [storeValueIn] is `null`, that means that: 36 | * 37 | * - this expectation cannot store value (it does not "emit" anything, like the 38 | * `either` construct which just executes branches and picks the first result), 39 | * or 40 | * 41 | * - this expectation can store values but we do not want to store it for this 42 | * expectation 43 | * 44 | * Parser algorithms assume that expectations always expect some tokens to be 45 | * present (e.g. if we run out of tokens, we fail immediately instead of letting 46 | * the expectation crash). If the expectation can handle situations where there 47 | * are not enough tokens, then it should implement the [HandlesTokenDrought] 48 | * marker interface. 49 | */ 50 | abstract class PangoroExpectation( 51 | /** 52 | * The name of the argument where the result of this expectation should be 53 | * stored, or `null` if the matched value of this expectation should not or 54 | * cannot be stored. 55 | */ 56 | val storeValueIn: String? = null 57 | ) { 58 | /** 59 | * Check if this expectation matches the given context at the given index 60 | * among the [context's tokens list][PangoroParsingContext.tokens]. 61 | * 62 | * The index is guaranteed to be in the bounds of the context's tokens, 63 | * unless the expectation signals that it can handle OOB tokens (i.e. it 64 | * implements [HandlesTokenDrought]). 65 | */ 66 | abstract fun matches( 67 | context: PangoroParsingContext, 68 | index: Int 69 | ): ExpectationResult 70 | } 71 | 72 | /** 73 | * Apply each expectation of this list to the given context starting at the 74 | * given index. 75 | */ 76 | fun List.apply( 77 | context: PangoroParsingContext, 78 | startAt: Int = 0 79 | ): ExpectationResult { 80 | var index = startAt 81 | val map = mutableMapOf() 82 | forEach { 83 | if(index >= context.tokens.size && it !is HandlesTokenDrought) { 84 | return ExpectationResult.DidNotMatch( 85 | "Expected more tokens, but ran out of tokens", 86 | index 87 | ) 88 | } 89 | val result = it.matches(context, index) 90 | if (result is ExpectationResult.Success) { 91 | map.putAll(result.stored) 92 | index = result.nextIndex 93 | } else { 94 | return result 95 | } 96 | } 97 | return ExpectationResult.Success(map, index) 98 | } 99 | 100 | /** 101 | * When an expectation is also of type HandlesTokenDrought, it means that the 102 | * expectation should be called even when running out of tokens. 103 | * 104 | * Marker interface: does not require you to implement anything. 105 | */ 106 | interface HandlesTokenDrought -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/ParserTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.lixy 4 | import guru.zoroark.lixy.matchers.matches 5 | import guru.zoroark.lixy.tokenType 6 | import guru.zoroark.pangoro.expectations.PangoroExpectedLixyToken 7 | import guru.zoroark.pangoro.expectations.PangoroExpectedNode 8 | import kotlin.test.* 9 | 10 | class ParserTest { 11 | 12 | class SimpleType : PangoroNode { 13 | companion object : PangoroNodeDeclaration { 14 | override fun make(args: PangoroTypeDescription) = SimpleType() 15 | } 16 | } 17 | 18 | class StoringType(val child: SimpleType) : PangoroNode { 19 | companion object : PangoroNodeDeclaration { 20 | override fun make(args: PangoroTypeDescription) = StoringType( 21 | args["child"] 22 | ) 23 | } 24 | } 25 | 26 | @Test 27 | fun storing_parser_test() { 28 | val tokenOne = tokenType() 29 | val parser = PangoroParser( 30 | types = listOf( 31 | PangoroDescribedType( 32 | type = SimpleType, 33 | expectations = listOf(PangoroExpectedLixyToken(tokenOne)) 34 | ), 35 | PangoroDescribedType( 36 | type = StoringType, 37 | expectations = listOf( 38 | PangoroExpectedNode( 39 | SimpleType, 40 | "child" 41 | ) 42 | ) 43 | ) 44 | ), 45 | rootType = StoringType 46 | ) 47 | val lexer = lixy { 48 | state { 49 | "hello" isToken tokenOne 50 | } 51 | } 52 | val tokens = lexer.tokenize("hello") 53 | val ast = parser.parse(tokens) 54 | @Suppress("USELESS_IS_CHECK") 55 | assertTrue(ast is StoringType) 56 | @Suppress("USELESS_IS_CHECK") 57 | assertTrue(ast.child is SimpleType) 58 | } 59 | 60 | @Test 61 | fun basic_parser_test() { 62 | val token = tokenType() 63 | val parser = PangoroParser( 64 | types = listOf( 65 | PangoroDescribedType( 66 | type = SimpleType, 67 | expectations = listOf(PangoroExpectedLixyToken(token)) 68 | ) 69 | ), 70 | rootType = SimpleType 71 | ) 72 | val lexer = lixy { 73 | state { 74 | "hello" isToken token 75 | } 76 | } 77 | val tokens = lexer.tokenize("hello") 78 | val ast = parser.parse(tokens) 79 | @Suppress("USELESS_IS_CHECK") 80 | assertTrue(ast is SimpleType) 81 | } 82 | 83 | data class NumberNode(val value: String) : PangoroNode { 84 | companion object : PangoroNodeDeclaration { 85 | override fun make(args: PangoroTypeDescription): NumberNode = 86 | NumberNode(args["value"]) 87 | } 88 | } 89 | 90 | data class AdditionNode(val first: NumberNode, val second: NumberNode) : 91 | PangoroNode { 92 | companion object : PangoroNodeDeclaration { 93 | override fun make(args: PangoroTypeDescription): AdditionNode = 94 | AdditionNode(args["first"], args["second"]) 95 | } 96 | } 97 | 98 | @Test 99 | fun addition_parser_test() { 100 | val tokenNumber = tokenType() 101 | val tokenPlus = tokenType() 102 | val lexer = lixy { 103 | state { 104 | matches("\\d+") isToken tokenNumber 105 | "+" isToken tokenPlus 106 | " ".ignore 107 | } 108 | } 109 | val tokens = lexer.tokenize("123 + 4567") 110 | val parser = PangoroParser( 111 | listOf( 112 | PangoroDescribedType( 113 | AdditionNode, listOf( 114 | PangoroExpectedNode(NumberNode, "first"), 115 | PangoroExpectedLixyToken(tokenPlus), 116 | PangoroExpectedNode(NumberNode, "second") 117 | ) 118 | ), 119 | PangoroDescribedType( 120 | NumberNode, 121 | listOf(PangoroExpectedLixyToken(tokenNumber, "value")) 122 | ) 123 | ), 124 | AdditionNode 125 | ) 126 | val ast = parser.parse(tokens) 127 | assertEquals(AdditionNode(NumberNode("123"), NumberNode("4567")), ast) 128 | } 129 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/DslTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.lixy 4 | import guru.zoroark.lixy.matchers.matches 5 | import guru.zoroark.lixy.tokenType 6 | import guru.zoroark.pangoro.dsl.expect 7 | import guru.zoroark.pangoro.dsl.pangoro 8 | import kotlin.test.* 9 | 10 | class DslTest { 11 | 12 | data class NumberNode(val value: String) : PangoroNode { 13 | companion object : PangoroNodeDeclaration { 14 | override fun make(args: PangoroTypeDescription): NumberNode = 15 | NumberNode(args["value"]) 16 | } 17 | } 18 | 19 | data class AdditionNode(val first: NumberNode, val second: NumberNode) : 20 | PangoroNode { 21 | companion object : PangoroNodeDeclaration { 22 | override fun make(args: PangoroTypeDescription): AdditionNode = 23 | AdditionNode(args["first"], args["second"]) 24 | } 25 | } 26 | 27 | @Test 28 | fun addition_parser_DSL_test() { 29 | val tokenNumber = tokenType() 30 | val tokenPlus = tokenType() 31 | val lexer = lixy { 32 | state { 33 | matches("\\d+") isToken tokenNumber 34 | "+" isToken tokenPlus 35 | " ".ignore 36 | } 37 | } 38 | val parser = pangoro { 39 | NumberNode { 40 | expect(tokenNumber) storeIn "value" 41 | } 42 | AdditionNode root { 43 | expect(NumberNode) storeIn "first" 44 | expect(tokenPlus) 45 | expect(NumberNode) storeIn "second" 46 | } 47 | } 48 | val tokens = lexer.tokenize("123 + 4567") 49 | val ast = parser.parse(tokens) 50 | assertEquals( 51 | AdditionNode( 52 | NumberNode("123"), 53 | NumberNode("4567") 54 | ), ast 55 | ) 56 | } 57 | 58 | @Test 59 | fun addition_parser_DSL_short_test() { 60 | val tokenNumber = tokenType() 61 | val tokenPlus = tokenType() 62 | val lexer = lixy { 63 | state { 64 | matches("\\d+") isToken tokenNumber 65 | "+" isToken tokenPlus 66 | " ".ignore 67 | } 68 | } 69 | val parser = pangoro { 70 | NumberNode { 71 | +tokenNumber % "value" 72 | } 73 | AdditionNode root { 74 | +NumberNode % "first" 75 | +tokenPlus 76 | +NumberNode % "second" 77 | } 78 | } 79 | val tokens = lexer.tokenize("123 + 4567") 80 | val ast = parser.parse(tokens) 81 | assertEquals( 82 | AdditionNode( 83 | NumberNode("123"), 84 | NumberNode("4567") 85 | ), ast 86 | ) 87 | } 88 | 89 | @Test 90 | fun cannot_declare_same_type_twice() { 91 | val token = tokenType() 92 | assertFailsWith { 93 | pangoro { 94 | NumberNode { 95 | expect(token) 96 | } 97 | NumberNode { 98 | error("Should not be called") 99 | } 100 | } 101 | }.apply { 102 | val msg = message 103 | assertNotNull(msg) 104 | assertTrue(msg.containsJvm("NumberNode") && msg.contains("already")) 105 | } 106 | } 107 | 108 | @Test 109 | fun cannot_declare_root_twice() { 110 | val token = tokenType() 111 | assertFailsWith { 112 | pangoro { 113 | AdditionNode root { 114 | expect(token) 115 | } 116 | NumberNode root { 117 | expect(token) 118 | } 119 | } 120 | }.apply { 121 | val msg = message 122 | assertNotNull(msg) 123 | assertTrue(msg.contains("root") && msg.contains("already")) 124 | } 125 | } 126 | 127 | @Test 128 | fun fail_if_root_not_declared() { 129 | val token = tokenType() 130 | assertFailsWith { 131 | pangoro { 132 | AdditionNode { 133 | expect(token) 134 | } 135 | NumberNode { 136 | expect(token) 137 | } 138 | } 139 | }.apply { 140 | val msg = message 141 | assertNotNull(msg) 142 | assertTrue(msg.contains("root") && msg.contains("never")) 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/EitherDslTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.lixy 4 | import guru.zoroark.lixy.matchers.anyOf 5 | import guru.zoroark.lixy.matchers.repeated 6 | import guru.zoroark.lixy.tokenType 7 | import guru.zoroark.pangoro.dsl.either 8 | import guru.zoroark.pangoro.dsl.expect 9 | import guru.zoroark.pangoro.dsl.or 10 | import guru.zoroark.pangoro.dsl.pangoro 11 | import kotlin.test.Test 12 | import kotlin.test.assertEquals 13 | 14 | sealed class Expression : PangoroNode { 15 | companion object : PangoroNodeDeclaration { 16 | override fun make(args: PangoroTypeDescription): Expression { 17 | return args["expr"] 18 | } 19 | } 20 | } 21 | 22 | data class Operation( 23 | val leftOperand: Expression, 24 | val operator: String, 25 | val rightOperand: Expression 26 | ) : Expression() { 27 | companion object : PangoroNodeDeclaration { 28 | override fun make(args: PangoroTypeDescription): Operation = 29 | Operation(args["left"], args["op"], args["right"]) 30 | } 31 | } 32 | 33 | data class Number( 34 | val value: String 35 | ) : Expression() { 36 | companion object : PangoroNodeDeclaration { 37 | override fun make(args: PangoroTypeDescription): Number = 38 | Number(args["value"]) 39 | } 40 | } 41 | 42 | class EitherDslTest { 43 | @Test 44 | fun test_either_dsl() { 45 | // Operation parser 46 | 47 | val tOperator = tokenType("tOperator") 48 | val tNumber = tokenType("tNumber") 49 | val tOpenPar = tokenType("tOpenPar") 50 | val tClosePar = tokenType("tClosePar") 51 | val lexer = lixy { 52 | state { 53 | anyOf("+", "-", "/", "*") isToken tOperator 54 | ('0'..'9').repeated isToken tNumber 55 | "(" isToken tOpenPar 56 | ")" isToken tClosePar 57 | } 58 | } 59 | val parser = pangoro { 60 | Expression root { 61 | either { 62 | expect(Number) storeIn "expr" 63 | } or { 64 | expect(tOpenPar) 65 | expect(Operation) storeIn "expr" 66 | expect(tClosePar) 67 | } 68 | } 69 | Number { 70 | expect(tNumber) storeIn "value" 71 | } 72 | Operation { 73 | expect(Expression) storeIn "left" 74 | expect(tOperator) storeIn "op" 75 | expect(Expression) storeIn "right" 76 | } 77 | } 78 | val str = "((3+4)/(1+(3-1)))" 79 | assertEquals( 80 | Operation( 81 | Operation( 82 | Number("3"), 83 | "+", 84 | Number("4") 85 | ), 86 | "/", 87 | Operation( 88 | Number("1"), 89 | "+", 90 | Operation( 91 | Number("3"), 92 | "-", 93 | Number("1") 94 | ) 95 | ) 96 | ), 97 | parser.parse(lexer.tokenize(str)) 98 | ) 99 | } 100 | 101 | @Test 102 | fun test_either_dsl_short_notation() { 103 | // Operation parser 104 | 105 | val tOperator = tokenType("tOperator") 106 | val tNumber = tokenType("tNumber") 107 | val tOpenPar = tokenType("tOpenPar") 108 | val tClosePar = tokenType("tClosePar") 109 | val lexer = lixy { 110 | state { 111 | anyOf("+", "-", "/", "*") isToken tOperator 112 | ('0'..'9').repeated isToken tNumber 113 | "(" isToken tOpenPar 114 | ")" isToken tClosePar 115 | } 116 | } 117 | val parser = pangoro { 118 | Expression root { 119 | either { 120 | +Number storeIn "expr" 121 | } or { 122 | +tOpenPar 123 | +Operation storeIn "expr" 124 | +tClosePar 125 | } 126 | } 127 | Number { 128 | +tNumber storeIn "value" 129 | } 130 | Operation { 131 | +Expression storeIn "left" 132 | +tOperator storeIn "op" 133 | +Expression storeIn "right" 134 | } 135 | } 136 | val str = "((3+4)/(1+(3-1)))" 137 | assertEquals( 138 | Operation( 139 | Operation( 140 | Number("3"), 141 | "+", 142 | Number("4") 143 | ), 144 | "/", 145 | Operation( 146 | Number("1"), 147 | "+", 148 | Operation( 149 | Number("3"), 150 | "-", 151 | Number("1") 152 | ) 153 | ) 154 | ), 155 | parser.parse(lexer.tokenize(str)) 156 | ) 157 | } 158 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/guru/zoroark/pangoro/ExpectedEitherTest.kt: -------------------------------------------------------------------------------- 1 | package guru.zoroark.pangoro 2 | 3 | import guru.zoroark.lixy.lixy 4 | import guru.zoroark.lixy.matchers.anyOf 5 | import guru.zoroark.lixy.matchers.matches 6 | import guru.zoroark.lixy.tokenType 7 | import guru.zoroark.pangoro.expectations.PangoroEitherBranch 8 | import guru.zoroark.pangoro.expectations.PangoroExpectedEither 9 | import guru.zoroark.pangoro.expectations.PangoroExpectedLixyToken 10 | import guru.zoroark.pangoro.expectations.PangoroExpectedNode 11 | import kotlin.test.* 12 | 13 | class ExpectedEitherTest { 14 | 15 | sealed class BranchOneOrTwo : PangoroNode { 16 | data class BranchOne(val oneWord: String) : BranchOneOrTwo() { 17 | companion object : PangoroNodeDeclaration { 18 | override fun make(args: PangoroTypeDescription) = 19 | BranchOne(args["oneWord"]) 20 | } 21 | } 22 | 23 | data class BranchTwo(val twoWord: String) : BranchOneOrTwo() { 24 | companion object : PangoroNodeDeclaration { 25 | override fun make(args: PangoroTypeDescription): BranchTwo = 26 | BranchTwo(args["twoWord"]) 27 | } 28 | } 29 | } 30 | 31 | @Test 32 | fun either_matches_first_branch() { 33 | val str = "test (hello 1)" 34 | val tWord = tokenType("tWord") 35 | val tPar = tokenType("tPar") 36 | val tOne = tokenType("tOne") 37 | val tTwo = tokenType("tTwo") 38 | val lexer = lixy { 39 | state { 40 | matches("1") isToken tOne 41 | matches("2") isToken tTwo 42 | matches("\\w+") isToken tWord 43 | anyOf("(", ")") isToken tPar 44 | " ".ignore 45 | } 46 | } 47 | val pee = PangoroExpectedEither( 48 | listOf( 49 | PangoroEitherBranch( 50 | listOf( 51 | PangoroExpectedLixyToken(tPar), 52 | PangoroExpectedNode( 53 | BranchOneOrTwo.BranchOne, 54 | "ambiguous" 55 | ), 56 | PangoroExpectedLixyToken(tPar) 57 | ) 58 | ), 59 | PangoroEitherBranch( 60 | listOf( 61 | PangoroExpectedLixyToken(tPar), 62 | PangoroExpectedNode( 63 | BranchOneOrTwo.BranchTwo, 64 | "ambiguous" 65 | ), 66 | PangoroExpectedLixyToken(tPar) 67 | ) 68 | ) 69 | ) 70 | ) 71 | val result = pee.matches( 72 | PangoroParsingContext( 73 | lexer.tokenize(str), 74 | mapOf( 75 | BranchOneOrTwo.BranchOne to PangoroDescribedType( 76 | BranchOneOrTwo.BranchOne, 77 | listOf( 78 | PangoroExpectedLixyToken(tWord, "oneWord"), 79 | PangoroExpectedLixyToken(tOne) 80 | ) 81 | ), 82 | BranchOneOrTwo.BranchTwo to PangoroDescribedType( 83 | BranchOneOrTwo.BranchTwo, 84 | listOf( 85 | PangoroExpectedLixyToken(tWord, "twoWord"), 86 | PangoroExpectedLixyToken(tTwo) 87 | ) 88 | ) 89 | ) 90 | ), 91 | 1 92 | ) 93 | assertTrue(result is ExpectationResult.Success) 94 | assertEquals( 95 | BranchOneOrTwo.BranchOne("hello"), 96 | result.stored["ambiguous"] 97 | ) 98 | } 99 | 100 | @Test 101 | fun either_matches_second_branch() { 102 | val str = "test (hey 2)" 103 | val tWord = tokenType("tWord") 104 | val tPar = tokenType("tPar") 105 | val tOne = tokenType("tOne") 106 | val tTwo = tokenType("tTwo") 107 | val lexer = lixy { 108 | state { 109 | matches("1") isToken tOne 110 | matches("2") isToken tTwo 111 | matches("\\w+") isToken tWord 112 | anyOf("(", ")") isToken tPar 113 | " ".ignore 114 | } 115 | } 116 | val pee = PangoroExpectedEither( 117 | listOf( 118 | PangoroEitherBranch( 119 | listOf( 120 | PangoroExpectedLixyToken(tPar), 121 | PangoroExpectedNode( 122 | BranchOneOrTwo.BranchOne, 123 | "ambiguous" 124 | ), 125 | PangoroExpectedLixyToken(tPar) 126 | ) 127 | ), 128 | PangoroEitherBranch( 129 | listOf( 130 | PangoroExpectedLixyToken(tPar), 131 | PangoroExpectedNode( 132 | BranchOneOrTwo.BranchTwo, 133 | "ambiguous" 134 | ), 135 | PangoroExpectedLixyToken(tPar) 136 | ) 137 | ) 138 | ) 139 | ) 140 | val result = pee.matches( 141 | PangoroParsingContext( 142 | lexer.tokenize(str), 143 | mapOf( 144 | BranchOneOrTwo.BranchOne to PangoroDescribedType( 145 | BranchOneOrTwo.BranchOne, 146 | listOf( 147 | PangoroExpectedLixyToken(tWord, "oneWord"), 148 | PangoroExpectedLixyToken(tOne) 149 | ) 150 | ), 151 | BranchOneOrTwo.BranchTwo to PangoroDescribedType( 152 | BranchOneOrTwo.BranchTwo, 153 | listOf( 154 | PangoroExpectedLixyToken(tWord, "twoWord"), 155 | PangoroExpectedLixyToken(tTwo) 156 | ) 157 | ) 158 | ) 159 | ), 160 | 1 161 | ) 162 | assertTrue(result is ExpectationResult.Success) 163 | assertEquals( 164 | BranchOneOrTwo.BranchTwo("hey"), 165 | result.stored["ambiguous"] 166 | ) 167 | } 168 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------