├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── settings.gradle.kts └── src └── main └── kotlin ├── Main.kt ├── ast ├── JsonArray.kt ├── JsonBoolean.kt ├── JsonFloat.kt ├── JsonInteger.kt ├── JsonNode.kt ├── JsonNull.kt ├── JsonObject.kt ├── JsonPrimitive.kt └── JsonString.kt └── parser ├── JsonArrayParser.kt ├── JsonBooleanParser.kt ├── JsonFloatParser.kt ├── JsonIntegerParser.kt ├── JsonNodeParser.kt ├── JsonNullParser.kt ├── JsonObjectParser.kt ├── JsonPrimitiveParser.kt ├── JsonStringParser.kt ├── base ├── AnyParser.kt ├── CharParser.kt ├── CommaParser.kt ├── Consumer.kt ├── ManyParser.kt ├── ManySeparatedParser.kt ├── Map.kt ├── Parser.kt ├── ParserResult.kt ├── StringParser.kt ├── Take.kt ├── TakeFirst.kt ├── TakeWhile.kt └── WhitespaceConsumer.kt └── dsl ├── Discard.kt └── ParserState.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alex Sokol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple JSON Parser in Kotlin 2 | ============================ 3 | 4 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Kotlin](https://img.shields.io/badge/language-Kotlin-orange.svg)](https://kotlinlang.org/) 5 | 6 | This is a simple JSON parser written in Kotlin, intended to demonstrate how a basic JSON parser can be implemented in a straightforward manner. It is designed for educational purposes and is not meant for production use. 7 | 8 | Article that describes how the parser works: https://dev.to/y9san9/json-parsing-from-scratch-in-kotlin-4al8 9 | 10 | Note 11 | ---- 12 | 13 | Please be aware that this parser has some limitations and does not support the following features: 14 | 15 | * Parsing of floating-point numbers 16 | * Detailed error reporting 17 | 18 | Features 19 | -------- 20 | 21 | * Lightweight and easy-to-understand JSON parsing logic. 22 | * Demonstration of key concepts in parsing JSON data. 23 | 24 | Usage 25 | ----- 26 | 27 | To use this JSON parser in your Kotlin project, you can clone this repository and include the relevant Kotlin files in your project. However, remember that this parser is not intended for real-world applications. 28 | 29 | License 30 | ------- 31 | 32 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 33 | 34 | Contributing 35 | ------------ 36 | 37 | Contributions are not accepted for this repository, as its purpose is purely educational. However, feel free to fork and modify it for personal use or as a learning exercise. 38 | 39 | Happy coding! 40 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.8.21" 3 | application 4 | } 5 | 6 | group = "me.y9san9" 7 | version = "1.0" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | 14 | 15 | kotlin { 16 | jvmToolchain(11) 17 | } 18 | 19 | application { 20 | mainClass.set("MainKt") 21 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "kotlin-simple-json" 3 | 4 | -------------------------------------------------------------------------------- /src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import ast.JsonNode 2 | import parser.base.Parser 3 | import parser.jsonNodeParser 4 | 5 | // Note: 6 | // Does not support: 7 | // - Escaping 8 | // - Floating point 9 | // - Error reporting 10 | 11 | fun main() { 12 | val parser: Parser = jsonNodeParser() 13 | 14 | // language=json 15 | val exampleJson = """ 16 | { 17 | "glossary": { 18 | "title": "example glossary", 19 | "escape": "\\ \" \/ \b \f \n \r \t \u002D", 20 | "pages": 1, 21 | "description": null, 22 | "stars": 4.2, 23 | "GlossDiv": { 24 | "id": -1, 25 | "title": "S", 26 | "GlossList": { 27 | "GlossEntry": { 28 | "ID": "SGML", 29 | "SortAs": "SGML", 30 | "GlossTerm": "Standard Generalized Markup Language", 31 | "Acronym": "SGML", 32 | "Abbrev": "ISO 8879:1986", 33 | "GlossDef": { 34 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 35 | "GlossSeeAlso": [ 36 | "GML", 37 | "XML" 38 | ] 39 | }, 40 | "GlossSee": "markup" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | """.trimIndent() 47 | 48 | println( 49 | parser.parse(exampleJson) 50 | ) 51 | 52 | println( 53 | parser.parse("12456, 567890") 54 | ) 55 | println( 56 | parser.parse("\"Test") 57 | ) 58 | println( 59 | parser.parse("") 60 | ) 61 | println( 62 | parser.parse("nul") 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonArray.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | data class JsonArray(val list: List) : JsonNode 4 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonBoolean.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | sealed interface JsonBoolean : JsonPrimitive { 4 | val boolean: Boolean 5 | } 6 | 7 | object JsonTrue : JsonBoolean { 8 | override val boolean = true 9 | override fun toString(): String = "JsonTrue" 10 | } 11 | 12 | object JsonFalse : JsonBoolean { 13 | override val boolean = false 14 | override fun toString(): String = "JsonFalse" 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonFloat.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | data class JsonFloat(val string: String) : JsonPrimitive { 4 | val float: Float get() = string.toFloat() 5 | val double: Double get() = string.toDouble() 6 | 7 | val floatOrNull: Float? get() = string.toFloatOrNull() 8 | val doubleOrNull: Double? get() = string.toDoubleOrNull() 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonInteger.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | data class JsonInteger(val string: String) : JsonPrimitive { 4 | val byte: Byte get() = string.toByte() 5 | val short: Short get() = string.toShort() 6 | val int: Int get() = string.toInt() 7 | val long: Long get() = string.toLong() 8 | 9 | val byteOrNull: Byte? get() = string.toByteOrNull() 10 | val shortOrNull: Short? get() = string.toShortOrNull() 11 | val intOrNull: Int? get() = string.toIntOrNull() 12 | val longOrNull: Long? get() = string.toLongOrNull() 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonNode.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | sealed interface JsonNode 4 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonNull.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | object JsonNull : JsonPrimitive { 4 | override fun toString(): String = "JsonNull" 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonObject.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | data class JsonObject(val map: Map) : JsonNode 4 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonPrimitive.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | sealed interface JsonPrimitive : JsonNode 4 | -------------------------------------------------------------------------------- /src/main/kotlin/ast/JsonString.kt: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | data class JsonString(val string: String) : JsonPrimitive 4 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonArrayParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonArray 4 | import parser.base.* 5 | import parser.dsl.parser 6 | 7 | fun jsonArrayParser(): Parser = parser { 8 | char('[') 9 | whitespace() 10 | val nodes = manySeparated( 11 | elementParser = jsonNodeParser(failOnRemaining = false), 12 | separatorConsumer = commaConsumer() 13 | ) 14 | whitespace() 15 | char(']') 16 | JsonArray(nodes) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonBooleanParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonBoolean 4 | import ast.JsonFalse 5 | import ast.JsonTrue 6 | import parser.base.* 7 | 8 | fun jsonBooleanParser(): Parser = anyParser(jsonTrueParser(), jsonFalseParser()) 9 | 10 | fun jsonTrueParser(): Parser = stringConsumer("true").map { JsonTrue } 11 | fun jsonFalseParser(): Parser = stringConsumer("false").map { JsonFalse } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonFloatParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonFloat 4 | import parser.base.Parser 5 | import parser.base.takeFirst 6 | import parser.base.takeWhile 7 | import parser.dsl.parser 8 | 9 | fun jsonFloatParser(): Parser = parser { 10 | if (source.isEmpty()) fail() 11 | 12 | val minus = takeFirst { it == '-' } 13 | val number = takeWhile(Char::isDigit) 14 | 15 | if (number.isEmpty()) fail() 16 | 17 | val point = takeFirst { it == '.' } 18 | val fraction = takeWhile(Char::isDigit) 19 | 20 | if (point.isEmpty()) fail() 21 | 22 | val exponent = takeFirst { it == 'E' || it == 'e' } 23 | val sign = takeFirst { it == '+' || it == '-' } 24 | val exponentValue = takeWhile(Char::isDigit) 25 | 26 | if (exponent.isEmpty() && (sign.isNotEmpty() || exponentValue.isNotEmpty())) fail() 27 | if (exponent.isNotEmpty() && exponentValue.isEmpty()) fail() 28 | 29 | val result = buildString { 30 | append(minus) 31 | append(number) 32 | append(point) 33 | append(fraction) 34 | append(exponent) 35 | append(sign) 36 | append(exponentValue) 37 | } 38 | 39 | if (result.isEmpty()) fail() 40 | 41 | JsonFloat(string = result) 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonIntegerParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonInteger 4 | import parser.base.Parser 5 | import parser.base.takeFirst 6 | import parser.base.takeWhile 7 | import parser.dsl.parser 8 | 9 | fun jsonIntegerParser(): Parser = parser { 10 | val minus = takeFirst { it == '-' } 11 | val string = takeWhile(Char::isDigit) 12 | 13 | if (string.isEmpty()) fail() 14 | 15 | JsonInteger(string = "$minus$string") 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonNodeParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonNode 4 | import parser.base.Parser 5 | import parser.base.any 6 | import parser.dsl.ParserState 7 | import parser.dsl.parser 8 | 9 | fun jsonNodeParser(failOnRemaining: Boolean = true): Parser = parser { 10 | val node = jsonNode() 11 | if (failOnRemaining && source.isNotEmpty()) fail() 12 | node 13 | } 14 | 15 | fun ParserState.jsonNode(): JsonNode = any( 16 | jsonPrimitiveParser(), 17 | jsonArrayParser(), 18 | jsonObjectParser() 19 | ) 20 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonNullParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonNull 4 | import parser.base.Parser 5 | import parser.base.map 6 | import parser.base.stringConsumer 7 | 8 | fun jsonNullParser(): Parser = stringConsumer(string = "null").map { JsonNull } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonObjectParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonNode 4 | import ast.JsonObject 5 | import parser.base.* 6 | import parser.dsl.parser 7 | 8 | fun jsonObjectParser(): Parser = parser { 9 | char('{') 10 | whitespace() 11 | val pairs = manySeparated( 12 | elementParser = pairParser(), 13 | separatorConsumer = commaConsumer() 14 | ) 15 | whitespace() 16 | char('}') 17 | 18 | JsonObject(pairs.toMap()) 19 | } 20 | 21 | private fun pairParser(): Parser> = parser { 22 | val key = jsonString() 23 | whitespace() 24 | char(':') 25 | whitespace() 26 | val node = jsonNode() 27 | key.string to node 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonPrimitiveParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonPrimitive 4 | import parser.base.Parser 5 | import parser.base.anyParser 6 | 7 | fun jsonPrimitiveParser(): Parser = anyParser( 8 | jsonNullParser(), 9 | jsonBooleanParser(), 10 | jsonFloatParser(), 11 | jsonIntegerParser(), 12 | jsonStringParser() 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/JsonStringParser.kt: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ast.JsonString 4 | import parser.base.* 5 | import parser.dsl.ParserState 6 | import parser.dsl.parser 7 | 8 | fun jsonStringParser(): Parser = parser { 9 | char('"') 10 | val string = many( 11 | anyParser( 12 | unescapedString(), 13 | specialCharacters() 14 | ) 15 | ).joinToString(separator = "") 16 | char('"') 17 | JsonString(string) 18 | } 19 | 20 | private fun unescapedString(): Parser = parser { 21 | takeWhile { char -> char != '"' && char != '\\' } 22 | .takeIf { it.isNotEmpty() } 23 | ?: fail() 24 | } 25 | 26 | private fun specialCharacters(): Parser = parser { 27 | char('\\') 28 | 29 | any( 30 | charParser('\"').string(), 31 | charParser('\\').string(), 32 | charParser('/').string(), 33 | charParser('b').map { "\b" }, 34 | charParser('f').map { "\u000c" }, 35 | charParser('n').map { "\n" }, 36 | charParser('r').map { "\r" }, 37 | charParser('t').map { "\t" }, 38 | unicodeCharacter().string() 39 | ) 40 | } 41 | 42 | private fun unicodeCharacter(): Parser = parser { 43 | char('u') 44 | val code = takeExact(n = 4) 45 | .toIntOrNull(radix = 16) 46 | ?: fail() 47 | Char(code) 48 | } 49 | 50 | fun ParserState.jsonString(): JsonString = jsonStringParser().parse() 51 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/AnyParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.parser 5 | 6 | fun anyParser(vararg parsers: Parser): Parser = anyParser(parsers.toList()) 7 | 8 | fun anyParser(list: List>): Parser = parser { any(list) } 9 | 10 | fun ParserState.any(vararg parsers: Parser): T = any(parsers.toList()) 11 | 12 | fun ParserState.any(list: List>): T { 13 | for (parser in list) { 14 | parser 15 | .tryParse() 16 | .onSuccess { value -> return value } 17 | } 18 | fail() 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/CharParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.discard 5 | import parser.dsl.parser 6 | 7 | fun charParser(char: Char): Parser = parser { char(char) } 8 | 9 | fun charConsumer(char: Char): Consumer = charParser(char).consume() 10 | 11 | fun ParserState.char(char: Char): Char { 12 | if (!source.startsWith(char)) fail() 13 | discard(n = 1) 14 | return char 15 | } 16 | 17 | fun Parser.string() = map { it.toString() } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/CommaParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.parser 5 | 6 | fun commaConsumer(): Consumer = parser { comma() } 7 | 8 | fun ParserState.comma() { 9 | whitespace() 10 | char(',') 11 | whitespace() 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/Consumer.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | typealias Consumer = Parser 4 | 5 | fun Parser<*>.consume(): Consumer = map { } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/ManyParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.parser 5 | 6 | fun manyParser(elementParser: Parser): Parser> = parser { many(elementParser) } 7 | 8 | fun ParserState.many(elementParser: Parser): List { 9 | val results: MutableList = mutableListOf() 10 | 11 | while (true) { 12 | val result = elementParser 13 | .tryParse() 14 | .getOrElse { return results } 15 | 16 | results += result 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/ManySeparatedParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.parser 5 | 6 | fun manySeparatedParser( 7 | elementParser: Parser, 8 | separatorConsumer: Consumer 9 | ): Parser> = parser { 10 | manySeparated(elementParser, separatorConsumer) 11 | } 12 | 13 | fun ParserState.manySeparated( 14 | elementParser: Parser, 15 | separatorConsumer: Consumer 16 | ): List { 17 | val first = elementParser.tryParse().getOrElse { return emptyList() } 18 | 19 | val elementWithSeparator = parser { 20 | separatorConsumer.parse() 21 | elementParser.parse() 22 | } 23 | 24 | return listOf(first) + many(elementWithSeparator) 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/Map.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | inline fun ParserResult.map(transform: (T) -> R): ParserResult = 4 | when (this) { 5 | ParserResult.Failure -> ParserResult.Failure 6 | is ParserResult.Success -> ParserResult.Success(transform(value), remaining) 7 | } 8 | 9 | inline fun Parser.map( 10 | crossinline transform: (T) -> R 11 | ): Parser = Parser { source -> 12 | val result = parse(source) 13 | result.map(transform) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/Parser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | fun interface Parser { 4 | fun parse(source: String): ParserResult 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/ParserResult.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | sealed interface ParserResult { 4 | data class Success(val value: T, val remaining: String) : ParserResult 5 | object Failure : ParserResult { 6 | override fun toString(): String = "Failure" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/StringParser.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.discard 5 | import parser.dsl.parser 6 | 7 | fun stringParser(string: String): Parser = parser { string(string) } 8 | 9 | fun stringConsumer(string: String): Consumer = stringParser(string).consume() 10 | 11 | fun ParserState.string(string: String): String { 12 | if (!source.startsWith(string)) fail() 13 | discard(string.length) 14 | return string 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/Take.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.discard 5 | import parser.dsl.parser 6 | 7 | fun takeExactParser(n: Int): Parser = parser { takeExact(n) } 8 | 9 | fun ParserState.takeExact(n: Int): String { 10 | val string = source.take(n) 11 | if (string.length != n) fail() 12 | discard(n) 13 | return string 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/TakeFirst.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.discard 5 | import parser.dsl.parser 6 | 7 | inline fun takeFirstParser( 8 | crossinline predicate: (Char) -> Boolean 9 | ): Parser = parser { takeFirst(predicate) } 10 | 11 | inline fun ParserState.takeFirst( 12 | predicate: (Char) -> Boolean 13 | ): String { 14 | val result = source.firstOrNull() ?: return "" 15 | if (predicate(result)) { 16 | discard(1) 17 | return result.toString() 18 | } 19 | return "" 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/TakeWhile.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.discard 5 | import parser.dsl.parser 6 | 7 | inline fun takeWhileParser( 8 | crossinline predicate: (Char) -> Boolean 9 | ): Parser = parser { takeWhile(predicate) } 10 | 11 | inline fun ParserState.takeWhile( 12 | predicate: (Char) -> Boolean 13 | ): String { 14 | val result = source.takeWhile(predicate) 15 | discard(result.length) 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/base/WhitespaceConsumer.kt: -------------------------------------------------------------------------------- 1 | package parser.base 2 | 3 | import parser.dsl.ParserState 4 | import parser.dsl.parser 5 | 6 | fun whitespaceConsumer(): Consumer = parser { whitespace() } 7 | 8 | fun ParserState.whitespace() { 9 | takeWhile { it.isWhitespace() } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/dsl/Discard.kt: -------------------------------------------------------------------------------- 1 | package parser.dsl 2 | 3 | fun ParserState.discard(n: Int) { 4 | try { 5 | source = source.substring(n) 6 | } catch (_: IndexOutOfBoundsException) { 7 | fail() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/parser/dsl/ParserState.kt: -------------------------------------------------------------------------------- 1 | package parser.dsl 2 | 3 | import parser.base.Parser 4 | import parser.base.ParserResult 5 | 6 | private class ParserFailure : RuntimeException() 7 | 8 | class ParserState( 9 | var source: String 10 | ) { 11 | fun Parser.parse(): T = 12 | when (val result = parse(source)) { 13 | ParserResult.Failure -> fail() 14 | is ParserResult.Success -> { 15 | source = result.remaining 16 | result.value 17 | } 18 | } 19 | 20 | fun Parser.tryParse(): Result { 21 | return try { 22 | val value = parse() 23 | Result.success(value) 24 | } catch (failure: ParserFailure) { 25 | Result.failure(failure) 26 | } 27 | } 28 | 29 | fun fail(): Nothing = throw ParserFailure() 30 | } 31 | 32 | fun parser(block: ParserState.() -> T): Parser = Parser { source -> 33 | try { 34 | val state = ParserState(source) 35 | val result = state.block() 36 | ParserResult.Success(result, state.source) 37 | } catch (_: ParserFailure) { 38 | ParserResult.Failure 39 | } 40 | } 41 | --------------------------------------------------------------------------------