From 47e8c9e639f2d9696639cc15c8924d962b45cab3 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Wed, 26 Oct 2022 18:04:32 +1100 Subject: [PATCH] refactor: Replace ANTLR version parser with a recursive decent parser #1615 --- .../au/com/dius/pact/core/support/Version.g4 | 19 ---- .../com/dius/pact/core/support/Version.java | 89 ------------------ .../au/com/dius/pact/core/support/Version.kt | 90 +++++++++++++++++++ .../core/support/VersionParserSpec.groovy | 22 ++++- 4 files changed, 111 insertions(+), 109 deletions(-) delete mode 100644 core/support/src/main/antlr/au/com/dius/pact/core/support/Version.g4 delete mode 100644 core/support/src/main/java/au/com/dius/pact/core/support/Version.java create mode 100644 core/support/src/main/kotlin/au/com/dius/pact/core/support/Version.kt diff --git a/core/support/src/main/antlr/au/com/dius/pact/core/support/Version.g4 b/core/support/src/main/antlr/au/com/dius/pact/core/support/Version.g4 deleted file mode 100644 index dfb507df7f..0000000000 --- a/core/support/src/main/antlr/au/com/dius/pact/core/support/Version.g4 +++ /dev/null @@ -1,19 +0,0 @@ -grammar Version; - -@header { - package au.com.dius.pact.core.support; -} - -version returns [ Version v ] : - { Integer major, minor, patch = null; } - INT { major = $INT.int; } '.' INT { minor = $INT.int; } ('.' INT { patch = $INT.int; })? EOF { - if (patch != null) { - $v = new Version(major, minor, patch); - } else { - $v = new Version(major, minor); - } - } - ; - -INT : DIGIT+ ; -fragment DIGIT : [0-9] ; diff --git a/core/support/src/main/java/au/com/dius/pact/core/support/Version.java b/core/support/src/main/java/au/com/dius/pact/core/support/Version.java deleted file mode 100644 index a5c76b7086..0000000000 --- a/core/support/src/main/java/au/com/dius/pact/core/support/Version.java +++ /dev/null @@ -1,89 +0,0 @@ -package au.com.dius.pact.core.support; - -import com.github.michaelbull.result.Err; -import com.github.michaelbull.result.Ok; -import com.github.michaelbull.result.Result; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.TokenStream; - -import java.util.Objects; - -public class Version { - private Integer major; - private Integer minor; - private Integer patch; - - public Version(Integer major, Integer minor, Integer patch) { - this.major = major; - this.minor = minor; - this.patch = patch; - } - - public Version(Integer major, Integer minor) { - this.major = major; - this.minor = minor; - } - - public Integer getMajor() { - return major; - } - - public void setMajor(Integer major) { - this.major = major; - } - - public Integer getMinor() { - return minor; - } - - public void setMinor(Integer minor) { - this.minor = minor; - } - - public Integer getPatch() { - return patch; - } - - public void setPatch(Integer patch) { - this.patch = patch; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Version version = (Version) o; - return Objects.equals(major, version.major) && - Objects.equals(minor, version.minor) && - Objects.equals(patch, version.patch); - } - - @Override - public int hashCode() { - return Objects.hash(major, minor, patch); - } - - @Override - public String toString() { - if (patch == null) { - return String.format("%d.%d", major, minor); - } else { - return String.format("%d.%d.%d", major, minor, patch); - } - } - - public static Result parse(String version) { - CharStream charStream = CharStreams.fromString(version); - VersionLexer lexer = new VersionLexer(charStream); - TokenStream tokens = new CommonTokenStream(lexer); - VersionParser parser = new VersionParser(tokens); - VersionParser.VersionContext result = parser.version(); - if (result.exception != null) { - return new Err(result.exception); - } else { - return new Ok(result.v); - } - } -} diff --git a/core/support/src/main/kotlin/au/com/dius/pact/core/support/Version.kt b/core/support/src/main/kotlin/au/com/dius/pact/core/support/Version.kt new file mode 100644 index 0000000000..4be483d409 --- /dev/null +++ b/core/support/src/main/kotlin/au/com/dius/pact/core/support/Version.kt @@ -0,0 +1,90 @@ +package au.com.dius.pact.core.support + +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result + +data class Version( + var major: Int, + var minor: Int, + var patch: Int? = null +) { + override fun toString(): String { + return if (patch == null) { + "$major.$minor" + } else { + "$major.$minor.$patch" + } + } + + companion object { + val INT = Regex("^\\d+") + + @JvmStatic + fun parse(version: String): Result { + var buffer = version + var index = 0 + + val major = when (val result = parseInt(buffer, index)) { + is Ok -> { + buffer = result.value.second + index = result.value.third + result.value.first + } + is Err -> return result + } + + when (val dot = parseChar('.', buffer, index)) { + is Ok -> { + buffer = dot.value.first + index = dot.value.second + } + is Err -> { + return dot + } + } + + val minor = when (val result = parseInt(buffer, index)) { + is Ok -> { + buffer = result.value.second + index = result.value.third + result.value.first + } + is Err -> return result + } + + val dot = parseChar('.', buffer, index) + return when { + dot is Ok -> { + buffer = dot.value.first + index = dot.value.second + when (val result = parseInt(buffer, index)) { + is Ok -> Ok(Version(major, minor, result.value.first)) + is Err -> result + } + } + buffer.isEmpty() -> Ok(Version(major, minor)) + else -> Err("Unexpected character '${buffer[0]}' at index $index") + } + } + + private fun parseChar(c: Char, buffer: String, index: Int): Result, String> { + return when { + buffer.isNotEmpty() && buffer[0] == c -> { + Ok(buffer.substring(1) to (index + 1)) + } + else -> Err("Was expecting a $c at index $index") + } + } + + private fun parseInt(buffer: String, index: Int): Result, String> { + return when (val result = INT.find(buffer)) { + null -> Err("Was expecting an integer at index $index") + else -> { + val i = result.value.toInt() + Ok(Triple(i, buffer.substring(result.value.length), index + result.value.length)) + } + } + } + } +} diff --git a/core/support/src/test/groovy/au/com/dius/pact/core/support/VersionParserSpec.groovy b/core/support/src/test/groovy/au/com/dius/pact/core/support/VersionParserSpec.groovy index a0ab05a2a1..43a1915f63 100644 --- a/core/support/src/test/groovy/au/com/dius/pact/core/support/VersionParserSpec.groovy +++ b/core/support/src/test/groovy/au/com/dius/pact/core/support/VersionParserSpec.groovy @@ -2,6 +2,7 @@ package au.com.dius.pact.core.support import spock.lang.Specification import org.antlr.v4.runtime.InputMismatchException +import spock.lang.Unroll class VersionParserSpec extends Specification { @@ -17,6 +18,25 @@ class VersionParserSpec extends Specification { def 'parse invalid version'() { expect: - Version.parse('lkzasdjskjdf').component2() instanceof InputMismatchException + Version.parse('lkzasdjskjdf').component2() == 'Was expecting an integer at index 0' + } + + @Unroll + def 'parse errors'() { + expect: + Version.parse(version).component2() == error + + where: + + version | error + '' | 'Was expecting an integer at index 0' + 'sdsd' | 'Was expecting an integer at index 0' + '0' | 'Was expecting a . at index 1' + '0sass' | 'Was expecting a . at index 1' + '100' | 'Was expecting a . at index 3' + '100.' | 'Was expecting an integer at index 4' + '100.10.' | 'Was expecting an integer at index 7' + '100.10x' | "Unexpected character 'x' at index 6" + '100.10.sss' | 'Was expecting an integer at index 7' } }