Skip to content

Commit 3f3f590

Browse files
committed
feat!: add Expression.FunctionCall
1 parent a6bbb61 commit 3f3f590

File tree

10 files changed

+186
-35
lines changed

10 files changed

+186
-35
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ plugins {
88
}
99

1010
group = 'io.typst'
11-
version = '3.2.0'
11+
version = '4.0.0'
1212

1313
repositories {
1414
mavenCentral()

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/BinaryOpType.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ enum class BinaryOpType(val bindingPowers: Pair<Int, Int>) {
55
MINUS(10 to 11),
66
MULTIPLY(20 to 21),
77
DIVIDE(20 to 21),
8+
POW(20 to 21),
89
;
910

1011
companion object {
@@ -14,6 +15,7 @@ enum class BinaryOpType(val bindingPowers: Pair<Int, Int>) {
1415
'-' -> MINUS
1516
'*' -> MULTIPLY
1617
'/' -> DIVIDE
18+
'^' -> POW
1719
else -> null
1820
}
1921
}

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/Evaluator.kt

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,27 @@ package io.typst.bukkit.kotlin.serialization.expression
22

33
import io.typst.bukkit.kotlin.serialization.expression.Expression.*
44
import io.vavr.control.Either
5+
import kotlin.math.*
56

67
fun Expression.evaluate(env: Map<String, Double>): Either<Failure, Double> {
7-
return Evaluator.evaluate(this, env)
8+
return Evaluator(env).evaluate(this)
89
}
910

1011
fun evaluate(xs: String, env: Map<String, Double>): Either<Failure, Double> =
11-
Evaluator.evaluate(xs, env)
12+
Evaluator(env).evaluate(xs)
1213

13-
object Evaluator {
14-
fun evaluate(xs: String, env: Map<String, Double>): Either<Failure, Double> =
15-
Lexer.lexAll(xs).flatMap {
14+
data class Evaluator(
15+
val env: Map<String, Double> = emptyMap(),
16+
) {
17+
fun evaluate(xs: String): Either<Failure, Double> {
18+
return Lexer.lexAll(xs).flatMap {
1619
Parser.parse(it)
1720
}.flatMap {
18-
evaluate(it, env)
21+
evaluate(it)
1922
}
23+
}
2024

21-
fun evaluate(expr: Expression, env: Map<String, Double>): Either<Failure, Double> {
25+
fun evaluate(expr: Expression): Either<Failure, Double> {
2226
when (expr) {
2327
is Literal -> {
2428
return Either.right(expr.value)
@@ -34,7 +38,7 @@ object Evaluator {
3438
}
3539

3640
is Unary -> {
37-
val valueEither = evaluate(expr.operand, env)
41+
val valueEither = evaluate(expr.operand)
3842
if (valueEither is Either.Left) {
3943
return valueEither
4044
}
@@ -46,11 +50,11 @@ object Evaluator {
4650
}
4751

4852
is Binary -> {
49-
val leftEither = evaluate(expr.left, env)
53+
val leftEither = evaluate(expr.left)
5054
if (leftEither is Either.Left) {
5155
return leftEither
5256
}
53-
val rightEither = evaluate(expr.right, env)
57+
val rightEither = evaluate(expr.right)
5458
if (rightEither is Either.Left) {
5559
return rightEither
5660
}
@@ -66,9 +70,55 @@ object Evaluator {
6670
} else {
6771
left / right
6872
}
73+
74+
BinaryOpType.POW -> left.pow(right)
6975
}
7076
return Either.right(result)
7177
}
78+
79+
is FunctionCall -> {
80+
val functionType = FunctionType.registry[expr.name]
81+
?: return Either.left(Failure("Unknown function: ${expr.name}", 0))
82+
if (expr.arguments.size != functionType.argumentSize) {
83+
return Either.left(
84+
Failure(
85+
"Expected function argument size ${functionType.argumentSize} but ${expr.arguments.size}",
86+
0
87+
)
88+
)
89+
}
90+
return when (functionType) {
91+
FunctionType.MIN ->
92+
evaluate(expr.arguments[0])
93+
.flatMap { a ->
94+
evaluate(expr.arguments[1]).map { b ->
95+
min(a, b)
96+
}
97+
}
98+
99+
FunctionType.MAX ->
100+
evaluate(expr.arguments[0])
101+
.flatMap { a ->
102+
evaluate(expr.arguments[1]).map { b ->
103+
max(a, b)
104+
}
105+
}
106+
107+
FunctionType.LOG ->
108+
evaluate(expr.arguments[0])
109+
.flatMap { a ->
110+
evaluate(expr.arguments[1]).map { b ->
111+
log(a, b)
112+
}
113+
}
114+
115+
FunctionType.LOG10 ->
116+
evaluate(expr.arguments[0]).map(::log10)
117+
118+
FunctionType.SQRT ->
119+
evaluate(expr.arguments[0]).map(::sqrt)
120+
}
121+
}
72122
}
73123
}
74124
}

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/Expression.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ sealed interface Expression {
1313
val left: Expression,
1414
val right: Expression,
1515
) : Expression
16+
17+
data class FunctionCall(
18+
val name: String,
19+
val arguments: List<Expression>,
20+
) : Expression
1621
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.typst.bukkit.kotlin.serialization.expression
2+
3+
import kotlin.math.min
4+
5+
enum class FunctionType(val label: String, val argumentSize: Int) {
6+
MIN("min", 2),
7+
MAX("max", 2),
8+
LOG("log", 2),
9+
LOG10("log10", 1),
10+
SQRT("sqrt", 1),
11+
;
12+
13+
companion object {
14+
val registry: Map<String, FunctionType> = FunctionType.entries.associateBy { it.label }
15+
}
16+
}

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/Lexer.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ object Lexer {
3030
hasDot = true
3131
end++
3232
} else {
33-
return Either.left(Failure("Non-digit char: $c", end))
33+
return Either.left(Failure("lex - Non-digit char: $c", end))
3434
}
3535
} else if (c.isDigit()) {
3636
end++
@@ -54,15 +54,16 @@ object Lexer {
5454
}
5555

5656
val tokenType = when (ch) {
57-
'+', '-', '*', '/' -> TokenType.OPERATOR
57+
'+', '-', '*', '/', '^' -> TokenType.OPERATOR
5858
'(' -> TokenType.LEFT_PAREN
5959
')' -> TokenType.RIGHT_PAREN
60+
',' -> TokenType.COMMA
6061
else -> null
6162
}
6263
return if (tokenType != null) {
6364
Either.right(Token(tokenType, ch.toString(), index, index + 1))
6465
} else {
65-
Either.left(Failure("Unknown char: $ch", index))
66+
Either.left(Failure("lex - Unknown char: $ch", index))
6667
}
6768
}
6869

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/Parser.kt

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,50 @@ object Parser {
77
return parseExpression(0, tokens, 0).flatMap { (expr, index) ->
88
val token = tokens.getOrNull(index)
99
if (token?.tokenType != TokenType.EOF) {
10-
Either.left(Failure("Unexpected token: ${token?.lexeme}", index))
10+
Either.left(Failure("parser - Unexpected token: ${token?.lexeme}", index))
1111
} else {
1212
Either.right(expr)
1313
}
1414
}
1515
}
1616

17+
private fun parseFunctionCall(
18+
name: String,
19+
tokens: List<Token>,
20+
i: Int,
21+
): Either<Failure, Pair<Expression, Int>> {
22+
val firstToken = tokens.getOrNull(i)
23+
?: return Either.left(Failure("parser - Unterminated function call: $name", i))
24+
if (firstToken.tokenType == TokenType.RIGHT_PAREN) {
25+
return Either.right(Expression.FunctionCall(name, emptyList()) to (i + 1))
26+
}
27+
val args = mutableListOf<Expression>()
28+
29+
var index = i
30+
while (true) {
31+
val either = parseExpression(0, tokens, index)
32+
if (either is Either.Left) {
33+
return either
34+
}
35+
val (argExpr, nextIndex) = either.get()
36+
args.add(argExpr)
37+
index = nextIndex
38+
val token = tokens.getOrNull(index)
39+
?: return Either.left(Failure("parser - Unterminated argument list in call to $name", index))
40+
when (token.tokenType) {
41+
TokenType.COMMA -> {
42+
index += 1
43+
}
44+
45+
TokenType.RIGHT_PAREN -> return Either.right(
46+
Expression.FunctionCall(name, args) to (index + 1)
47+
)
48+
49+
else -> return Either.left(Failure("parser - Expected ',' or ')' in argument list of $name", index))
50+
}
51+
}
52+
}
53+
1754
private fun parsePrefix(tokens: List<Token>, i: Int): Either<Failure, Pair<Expression, Int>> {
1855
val current = tokens[i]
1956
return when (current.tokenType) {
@@ -23,7 +60,13 @@ object Parser {
2360
}
2461

2562
TokenType.IDENTIFIER -> {
26-
Either.right(Expression.Variable(current.lexeme) to (i + 1))
63+
val name = current.lexeme
64+
val next = tokens.getOrNull(i + 1)
65+
if (next?.tokenType == TokenType.LEFT_PAREN) {
66+
parseFunctionCall(name, tokens, i + 2)
67+
} else {
68+
Either.right(Expression.Variable(current.lexeme) to (i + 1))
69+
}
2770
}
2871

2972
TokenType.OPERATOR -> {
@@ -35,21 +78,21 @@ object Parser {
3578
Expression.Unary(unaryOp, operand) to newIndex
3679
}
3780
} else {
38-
Either.left(Failure("Unexpected prefix operator: $op", i))
81+
Either.left(Failure("parser - Unexpected prefix operator: $op", i))
3982
}
4083
}
4184

4285
TokenType.LEFT_PAREN -> {
4386
parseExpression(0, tokens, i + 1)
4487
.flatMap { (expr, newIndex) ->
4588
if (tokens[newIndex].tokenType != TokenType.RIGHT_PAREN) {
46-
Either.left(Failure("Expected ')' to close '('", newIndex))
89+
Either.left(Failure("parser - Expected ')' to close '('", newIndex))
4790
} else Either.right(expr to (newIndex + 1))
4891
}
4992
}
5093

5194
else -> {
52-
Either.left(Failure("Unexpected token: ${current.lexeme}", i))
95+
Either.left(Failure("parser - Unexpected token: ${current.lexeme}", i))
5396
}
5497
}
5598
}
@@ -73,7 +116,7 @@ object Parser {
73116
val op = token.lexeme
74117
val binaryOp = BinaryOpType.from(op[0])
75118
val (leftBp, rightBp) = binaryOp?.bindingPowers
76-
?: return Either.left(Failure("Unknown operator: $op", newIndex))
119+
?: return Either.left(Failure("parser - Unknown operator: $op", newIndex))
77120
if (leftBp < minBp) break
78121
val rightEither = parseExpression(rightBp, tokens, newIndex + 1)
79122
if (rightEither is Either.Left) {

src/main/kotlin/io/typst/bukkit/kotlin/serialization/expression/TokenType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ enum class TokenType {
77
LEFT_PAREN,
88
RIGHT_PAREN,
99
EOF,
10+
COMMA,
1011
}

0 commit comments

Comments
 (0)