Skip to content

Commit

Permalink
Interpreter: Support more expressions and statements
Browse files Browse the repository at this point in the history
  • Loading branch information
mattco98 committed Oct 5, 2022
1 parent ec8b5f9 commit 50535aa
Showing 1 changed file with 230 additions and 16 deletions.
246 changes: 230 additions & 16 deletions src/main/kotlin/org/serenityos/jakt/comptime/Interpreter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import com.intellij.psi.PsiElement
import com.intellij.refactoring.suggested.endOffset
import com.intellij.refactoring.suggested.startOffset
import org.serenityos.jakt.JaktFile
import org.serenityos.jakt.psi.JaktPsiElement
import org.serenityos.jakt.psi.JaktScope
import org.serenityos.jakt.psi.ancestors
import org.serenityos.jakt.JaktTypes
import org.serenityos.jakt.psi.*
import org.serenityos.jakt.psi.api.*
import org.serenityos.jakt.psi.descendantOfType
import org.serenityos.jakt.psi.reference.hasNamespace
import org.serenityos.jakt.utils.unreachable

Expand Down Expand Up @@ -67,7 +65,28 @@ class Interpreter(element: JaktPsiElement) {
/*** EXPRESSIONS ***/

is JaktMatchExpression -> TODO("comptime match expressions")
is JaktTryExpression -> TODO("comptime try expressions")
is JaktTryExpression -> {
when (val result = evaluate(element.expression ?: element.blockList[0])) {
is ExecutionResult.Throw -> if (element.catchKeyword != null) {
val pushedScope = if (element.catchDecl != null) {
val newScope = Scope(scope)
newScope[element.catchDecl!!.identifier.text] = result.value
pushScope(newScope)
true
} else false

evaluate(element.blockList[1])

if (pushedScope)
popScope()

}
!is ExecutionResult.Normal -> return result
else -> {}
}

ExecutionResult.Normal(VoidValue)
}
is JaktLambdaExpression -> TODO("comptime lambdas")
is JaktAssignmentBinaryExpression -> {
val binaryOp = when {
Expand Down Expand Up @@ -106,15 +125,15 @@ class Interpreter(element: JaktPsiElement) {
error("Unknown identifier \"$name\"", assignmentTarget)
}
is JaktIndexedAccessExpression -> {
val target = evaluateNonYield(element.left) { return it }
val target = evaluateNonYield(assignmentTarget.expressionList[0]) { return it }

if (target !is ArrayValue)
error("Expected array, found ${target.typeName()}", element.left)
error("Expected array, found ${target.typeName()}", assignmentTarget.expressionList[0])

val index = evaluateNonYield(element.right!!) { return it }
val index = evaluateNonYield(assignmentTarget.expressionList[1]!!) { return it }

if (index !is IntegerValue)
error("Expected integer, found ${index.typeName()}", element.right!!)
error("Expected integer, found ${index.typeName()}", assignmentTarget.expressionList[1]!!)

if (index.value.toInt() > target.values.size)
error(
Expand All @@ -125,11 +144,11 @@ class Interpreter(element: JaktPsiElement) {
target.values[index.value.toInt()] = newValue
}
is JaktAccessExpression -> {
val target = evaluateNonYield(element.left) { return it }
val target = evaluateNonYield(assignmentTarget.expression) { return it }

if (assignmentTarget.decimalLiteral != null) {
if (target !is TupleValue)
error("Expected tuple, found ${target.typeName()}", element.left)
error("Expected tuple, found ${target.typeName()}", assignmentTarget.expression)

val index = assignmentTarget.decimalLiteral!!.text.toInt()
if (index > target.values.size)
Expand Down Expand Up @@ -231,7 +250,33 @@ class Interpreter(element: JaktPsiElement) {
)
is JaktCastExpression -> TODO("comptime casts")
is JaktIsExpression -> TODO("comptime is expressions")
is JaktUnaryExpression -> TODO("comptime unary expressions")
is JaktUnaryExpression -> applyUnaryOperator(
element.expression,
when {
element.minus != null -> UnaryOperator.Minus
element.keywordNot != null -> UnaryOperator.Not
element.tilde != null -> UnaryOperator.BitwiseNot
element.ampersand != null -> when {
element.rawKeyword != null -> UnaryOperator.RawReference
element.mutKeyword != null -> UnaryOperator.MutReference
else -> UnaryOperator.Reference
}
element.asterisk != null -> UnaryOperator.Dereference
else -> {
val plusPlus = element.findChildOfType(JaktTypes.PLUS_PLUS)
if (plusPlus != null) {
if (plusPlus.startOffset < element.expression.startOffset) {
UnaryOperator.PrefixIncrement
} else UnaryOperator.PostfixIncrement
} else {
val minusMinus = element.findChildOfType(JaktTypes.MINUS_MINUS)!!
if (minusMinus.startOffset < element.expression.startOffset) {
UnaryOperator.PrefixDecrement
} else UnaryOperator.PostfixDecrement
}
}
}
)
is JaktBooleanLiteral -> ExecutionResult.Normal(BoolValue(element.trueKeyword != null))
is JaktNumericLiteral -> {
element.binaryLiteral?.let {
Expand Down Expand Up @@ -424,8 +469,35 @@ class Interpreter(element: JaktPsiElement) {

ExecutionResult.Normal(VoidValue)
}
is JaktWhileStatement -> TODO("comptime while statements")
is JaktLoopStatement -> TODO("comptime loop statements")
is JaktWhileStatement -> {
while (true) {
val exprResult = evaluateNonYield(element.expression) { return it }
if (exprResult !is BoolValue)
error("Expected bool value, found ${exprResult.typeName()}", element.expression)

if (!exprResult.value)
break

when (val blockResult = evaluate(element.block)) {
is ExecutionResult.Break -> break
is ExecutionResult.Continue, is ExecutionResult.Normal -> {}
else -> return blockResult
}
}

ExecutionResult.Normal(VoidValue)
}
is JaktLoopStatement -> {
while (true) {
when (val blockResult = evaluate(element.block)) {
is ExecutionResult.Break -> break
is ExecutionResult.Continue, is ExecutionResult.Normal -> {}
else -> return blockResult
}
}

ExecutionResult.Normal(VoidValue)
}
is JaktForStatement -> TODO("comptime for statements")
is JaktVariableDeclarationStatement -> {
if (element.parenOpen != null) {
Expand All @@ -440,7 +512,20 @@ class Interpreter(element: JaktPsiElement) {

ExecutionResult.Normal(VoidValue)
}
is JaktGuardStatement -> TODO("comptime guard statements")
is JaktGuardStatement -> {
val condition = evaluateNonYield(element.expression) { return it }
if (condition !is BoolValue)
error("Expected bool value, found ${condition.typeName()}", element.expression)

if (condition.value) {
when (val result = evaluate(element.block)) {
is ExecutionResult.Normal -> error("Unexpected fallthrough from guard block", element.block)
else -> return result
}
}

ExecutionResult.Normal(VoidValue)
}
is JaktYieldStatement -> ExecutionResult.Yield(evaluateNonYield(element.expression) { return it })
is JaktBreakStatement -> ExecutionResult.Break
is JaktContinueStatement -> ExecutionResult.Continue
Expand All @@ -451,7 +536,7 @@ class Interpreter(element: JaktPsiElement) {
try {
element.statementList.forEach {
val result = evaluate(it)
if (result is ExecutionResult.Yield || result is ExecutionResult.Return)
if (result !is ExecutionResult.Normal)
return result
}
ExecutionResult.Normal(VoidValue)
Expand Down Expand Up @@ -628,6 +713,120 @@ class Interpreter(element: JaktPsiElement) {
return ExecutionResult.Normal(value)
}

private fun applyUnaryOperator(expr: JaktExpression, op: UnaryOperator): ExecutionResult {
return when (op) {
UnaryOperator.PostfixIncrement, UnaryOperator.PostfixDecrement,
UnaryOperator.PrefixIncrement, UnaryOperator.PrefixDecrement -> {
val isPrefix = op == UnaryOperator.PrefixIncrement || op == UnaryOperator.PrefixDecrement
val isIncrement = op == UnaryOperator.PrefixIncrement || op == UnaryOperator.PostfixIncrement

val delta = if (isIncrement) 1 else -1

val currValue = evaluateNonYield(expr) { return it }
val newValue = when (currValue) {
is IntegerValue -> IntegerValue(currValue.value + delta)
is FloatValue -> FloatValue(currValue.value + delta)
else -> error("Invalid type ${currValue.typeName()} for numeric prefix operator '${op.op}'", expr)
}

assign(expr, newValue)?.let { return it }

ExecutionResult.Normal(if (isPrefix) newValue else currValue)
}
UnaryOperator.Minus -> {
val value = evaluateNonYield(expr) { return it }
when (value) {
is IntegerValue -> IntegerValue(-value.value)
is FloatValue -> FloatValue(-value.value)
else -> error("Invalid type ${value.typeName()} for unary integer '-' operator", expr)
}.let(ExecutionResult::Normal)
}
UnaryOperator.Not -> {
val value = evaluateNonYield(expr) { return it }
if (value is BoolValue) {
ExecutionResult.Normal(BoolValue(!value.value))
} else {
error("Invalid type ${value.typeName()} for boolean 'not' operator", expr)
}
}
UnaryOperator.BitwiseNot -> {
val value = evaluateNonYield(expr) { return it }
if (value is IntegerValue) {
ExecutionResult.Normal(IntegerValue(value.value.inv()))
} else {
error("Invalid type ${value.typeName()} for integer '~' operator", expr)
}
}
UnaryOperator.Reference -> TODO("comptime unary reference operator")
UnaryOperator.RawReference -> TODO("comptime unary reference operator")
UnaryOperator.MutReference -> TODO("comptime unary reference operator")
UnaryOperator.Dereference -> TODO("comptime unary dereference operator")
UnaryOperator.Unwrap -> {
val value = evaluateNonYield(expr) { return it }

when (value) {
is OptionalValue -> if (value.value == null) {
error("Attempt to unwrap empty optional value", expr)
} else {
ExecutionResult.Normal(value.value)
}
else -> error("Invalid type ${value.typeName()} for optional unwrap operator '!'", expr)
}
}
}
}

private fun assign(expr: JaktExpression, value: Value): ExecutionResult? {
when (expr) {
is JaktPlainQualifierExpression -> {
if (expr.plainQualifier.hasNamespace)
error("Invalid assignment target", expr)

val name = expr.plainQualifier.name!!
if (!assign(name, value, initialize = false))
error("Unknown identifier \"$name\"", expr)
}
is JaktIndexedAccessExpression -> {
val target = evaluateNonYield(expr.expressionList[0]) { return it }

if (target !is ArrayValue)
error("Expected array, found ${target.typeName()}", expr.expressionList[0])

val index = evaluateNonYield(expr.expressionList[1]!!) { return it }

if (index !is IntegerValue)
error("Expected integer, found ${index.typeName()}", expr.expressionList[1]!!)

if (index.value.toInt() > target.values.size)
error(
"Out-of-bounds assignment to array of length ${target.values.size} with index ${index.value}",
expr
)

target.values[index.value.toInt()] = value
}
is JaktAccessExpression -> {
val target = evaluateNonYield(expr.expression) { return it }

if (expr.decimalLiteral != null) {
if (target !is TupleValue)
error("Expected tuple, found ${target.typeName()}", expr.expression)

val index = expr.decimalLiteral!!.text.toInt()
if (index > target.values.size)
error("Cannot assign to index $index of tuple of length ${target.values.size}")

target.values[index] = value
} else {
target[expr.identifier!!.text] = value
}
}
else -> error("Invalid assignment target", expr)
}

return null
}

private fun assign(name: String, value: Value, initialize: Boolean): Boolean {
// TODO: Ensure bindings already exists in the scope

Expand Down Expand Up @@ -673,6 +872,21 @@ class Interpreter(element: JaktPsiElement) {

fun error(message: String, range: TextRange): Nothing = throw InterpreterException(message, range)

enum class UnaryOperator(val op: String) {
PrefixIncrement("++"),
PrefixDecrement("--"),
PostfixIncrement("++"),
PostfixDecrement("--"),
Minus("-"),
Not("not"),
BitwiseNot("~"),
Reference("&"),
RawReference("&raw"),
MutReference("&mut"),
Dereference("*"),
Unwrap("!"),
}

enum class BinaryOperator(val op: String) {
LogicalOr("or"),
LogicalAnd("and"),
Expand Down

0 comments on commit 50535aa

Please # to comment.