Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed Aug 15, 2024
0 parents commit bd43e44
Show file tree
Hide file tree
Showing 104 changed files with 2,830 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.gradle
build
.idea
.kotlin
.DS_Store
/gradle/wrapper/gradle-wrapper.jar
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Calkt

Calkt is a Kotlin library that supports parsing and calculating
various expressions. Parser is written in a way to have an ability
to be extended.

<!-- TOC -->
* [Calkt](#calkt)
* [Example](#example)
* [Implementation](#implementation)
* [Modules](#modules)
* [core](#core)
* [math](#math)
* [units](#units)
* [example](#example-1)
<!-- TOC -->

## Example

The result of running [example/Main.kt](example/src/main/kotlin/Main.kt):

```text
Enter math expression to calculate: 2 + 2 * 2
Parsed as: 2 plus (2 times 2)
Result: 6
Enter an expression with units to calculate: (1km - 0.5mi) + 2 yd to inches
Parsed as: (((1 Kilometers) minus (0.5 Miles)) plus (2 Yards)).convert(Inches)
Result: 7886.272 Inches
```

## Implementation

Work In Progress. Will be available at maven central soon.
Now you can use workarounds like [jitpack.io](https://jitpack.io/#y9san9/calkt/-SNAPSHOT):

Add Jitpack repo in the root build.gradle at the end of repositories:

```kotlin
dependencyResolutionManagement {
repositories {
mavenCentral()
maven("https://jitpack.io")
}
}
```

Step 2. Add the dependency
```kotlin
dependencies {
implementation("com.github.y9san9:calkt:-SNAPSHOT")
}
```

## Modules

The library consists of several different modules:

### core

Module with all basic types for parsing and calculating. Here
you can find `ParseContext` and `CalculateContext` as well as
functions to launch parsers/calculators.

### math

Module with implementation of basic math expressions that any
calculator can calculate. This is where you can find logic to
calculate numbers combined with basic supported operators (
`+`, `-`, `*`, `/`). You can implement your own operator, like
in [this example](example/src/main/kotlin/operator/ModOperator.kt) (% operator).

### units

Module with implementation of math expressions with units. It
depends on `math` module heavily and does not do any calculations
on its own. There is a logic to support calculation and
conversions of units like `1 km + 2`, `1km to inches`, etc.

### example

Module with all the examples you need to know. If something is missing,
PRs are welcome.
7 changes: 7 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}

dependencies {
api(libs.kotlin.gradle.plugin)
}
12 changes: 12 additions & 0 deletions build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
}

versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
26 changes: 26 additions & 0 deletions build-logic/src/main/kotlin/Version.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import org.gradle.api.Project

fun Project.versionFromProperties(acceptor: (String) -> Unit) {
afterEvaluate {
acceptor(versionFromProperties())
}
}

fun Project.versionFromProperties(): String {
val snapshot = project.findProperty("snapshot")?.toString()?.toBooleanStrict()
if (snapshot == null || !snapshot) return project.version.toString()

val commit = project.property("commit").toString()
val attempt = project.property("attempt").toString().toInt()

val version = buildString {
append(project.version)
append("-build")
append(commit.take(n = 7))
if (attempt > 1) {
append(attempt)
}
}

return version
}
25 changes: 25 additions & 0 deletions build-logic/src/main/kotlin/kmp-library-convention.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
kotlin("multiplatform")
id("publication-convention")
}

kotlin {
compilerOptions {
allWarningsAsErrors = true
progressiveMode = true
}

explicitApi()

jvmToolchain(17)

jvm()

js(IR) {
browser()
nodejs()
}
iosArm64()
iosX64()
iosSimulatorArm64()
}
11 changes: 11 additions & 0 deletions build-logic/src/main/kotlin/print-version-convention.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import org.gradle.kotlin.dsl.creating

tasks {
val printVersion by creating {
group = "CI"

doFirst {
println(versionFromProperties())
}
}
}
24 changes: 24 additions & 0 deletions build-logic/src/main/kotlin/publication-convention.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id("org.gradle.maven-publish")
}

group = "app.meetacy.ksm"

publishing {
repositories {
maven {
name = "GitHub"
url = uri("https://maven.pkg.github.com/meetacy/ksm")
credentials {
username = System.getenv("GITHUB_USERNAME")
password = System.getenv("GITHUB_TOKEN")
}
}
}

publications.withType<MavenPublication> {
versionFromProperties { version ->
this.version = version
}
}
}
21 changes: 21 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "2.0.0"
}

group = "me.y9san9"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
testImplementation(kotlin("test"))
}

tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(17)
}
9 changes: 9 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("kmp-library-convention")
}

dependencies {
commonMainImplementation(libs.bignum)
commonTestImplementation(projects.math)
commonTestImplementation(projects.units)
}
8 changes: 8 additions & 0 deletions core/src/commonMain/kotlin/me/y9san9/calkt/Expression.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@file:OptIn(ExperimentalSubclassOptIn::class)

package me.y9san9.calkt

import me.y9san9.calkt.annotation.ExpressionSubclass

@SubclassOptInRequired(ExpressionSubclass::class)
public interface Expression
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.y9san9.calkt.annotation

@RequiresOptIn(
message = "Usage of CalculateResult type is heavily dependent on knowledge of all subclasses. " +
"So when you subclass CalculateResult those places might brake, be careful",
)
public annotation class CalculateSubclass
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.y9san9.calkt.annotation

@RequiresOptIn(
message = "Usage of Expression type is heavily dependent on knowledge of all subclasses. " +
"So when you subclass Expression those places might brake, be careful",
)
public annotation class ExpressionSubclass
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.y9san9.calkt.annotation

@RequiresOptIn(
message = "Usage of FailureCause type is heavily dependent on knowledge of all subclasses. " +
"So when you subclass FailureCause those places might brake, be careful",
)
public annotation class FailureCauseSubclass
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package me.y9san9.calkt.calculate

import me.y9san9.calkt.Expression

public class CalculateContext(
public val expression: Expression,
public val calculateFunction: CalculateFunction,
public val precision: Long
) {
public val context: CalculateContext get() = this

public fun tryCalculate(function: CalculateFunction): CalculateResult {
return tryCalculate(expression, precision, function, calculateFunction)
}

public fun recursive(expression: Expression): CalculateResult.Success {
val context = CalculateContext(expression, calculateFunction, precision)
return calculateFunction(context)
}

public fun unsupportedExpression(): Nothing {
fail(CalculateResult.UnsupportedExpression)
}

public fun fail(
failure: CalculateResult.Failure
): Nothing {
throw FailureException(failure)
}

public class FailureException(public val failure: CalculateResult.Failure) : Throwable()
}

public fun tryCalculate(
expression: Expression,
precision: Long,
function: CalculateFunction
): CalculateResult {
return tryCalculate(expression, precision, function, function)
}

public fun tryCalculate(
expression: Expression,
precision: Long,
function: CalculateFunction,
recursive: CalculateFunction
): CalculateResult {
return try {
val context = CalculateContext(expression, recursive, precision)
function(context)
} catch (exception: CalculateContext.FailureException) {
exception.failure
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package me.y9san9.calkt.calculate

public fun interface CalculateFunction {
public operator fun invoke(context: CalculateContext): CalculateResult.Success
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package me.y9san9.calkt.calculate

import me.y9san9.calkt.calculate.CalculateResult.UnsupportedExpression

private class CombinedCalculateFunction(
val functions: List<CalculateFunction>
) : CalculateFunction {
override fun invoke(context: CalculateContext): CalculateResult.Success {
for (function in functions) {
val result = context.tryCalculate(function)
if (result is UnsupportedExpression) continue
return result.getOrFail(context)
}
context.unsupportedExpression()
}
}

public operator fun CalculateFunction.plus(
other: CalculateFunction
): CalculateFunction {
val functions = when {
this is CombinedCalculateFunction && other is CombinedCalculateFunction -> this.functions + other.functions
this is CombinedCalculateFunction -> this.functions + other
other is CombinedCalculateFunction -> listOf(this) + other.functions
else -> listOf(this, other)
}
return CombinedCalculateFunction(functions)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package me.y9san9.calkt.calculate

import me.y9san9.calkt.annotation.CalculateSubclass

public sealed interface CalculateResult {
@OptIn(ExperimentalSubclassOptIn::class)
@SubclassOptInRequired(CalculateSubclass::class)
public interface Failure : CalculateResult

@OptIn(CalculateSubclass::class)
public data object UnsupportedExpression : Failure

@OptIn(CalculateSubclass::class)
public data object DivisionByZero : Failure

@OptIn(ExperimentalSubclassOptIn::class)
@SubclassOptInRequired(CalculateSubclass::class)
public interface Success : CalculateResult
}

public fun CalculateResult.getOrFail(context: CalculateContext): CalculateResult.Success {
return when (this) {
is CalculateResult.Failure -> context.fail(failure = this)
is CalculateResult.Success -> this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.y9san9.calkt.internal

@PublishedApi
internal inline fun StringBuilder.withIndent(
indent: String = " ",
block: StringBuilder.() -> Unit
) {
val string = buildString(block).prependIndent(indent)
append(string)
}
Loading

0 comments on commit bd43e44

Please sign in to comment.