better checks for mutability, sipport for inplace mutation with assign()
This commit is contained in:
parent
ff03f3066d
commit
ca93d73b9c
@ -76,13 +76,16 @@ Assignemnt is an expression that changes its lvalue and return assigned value:
|
|||||||
|
|
||||||
As the assignment itself is an expression, you can use it in strange ways. Just remember
|
As the assignment itself is an expression, you can use it in strange ways. Just remember
|
||||||
to use parentheses as assignment operation insofar is left-associated and will not
|
to use parentheses as assignment operation insofar is left-associated and will not
|
||||||
allow chained assignments (we might fix it later)
|
allow chained assignments (we might fix it later). Use parentheses insofar:
|
||||||
|
|
||||||
var x = 0
|
var x = 0
|
||||||
var y = 0
|
var y = 0
|
||||||
x = (y = 5)
|
x = (y = 5)
|
||||||
x + y
|
assert(x==5)
|
||||||
>>> 10
|
assert(y==5)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Note that assignment operator returns rvalue, it can't be assigned.
|
||||||
|
|
||||||
## Modifying arithmetics
|
## Modifying arithmetics
|
||||||
|
|
||||||
@ -98,6 +101,11 @@ There is a set of assigning operations: `+=`, `-=`, `*=`, `/=` and even `%=`.
|
|||||||
|
|
||||||
Notice the parentheses here: the assignment has low priority!
|
Notice the parentheses here: the assignment has low priority!
|
||||||
|
|
||||||
|
These operators return rvalue, unmodifiable.
|
||||||
|
|
||||||
|
## Assignemnt return r-value!
|
||||||
|
|
||||||
|
|
||||||
## Math
|
## Math
|
||||||
|
|
||||||
It is rather simple, like everywhere else:
|
It is rather simple, like everywhere else:
|
||||||
@ -110,7 +118,7 @@ See [math](math.md) for more on it. Notice using Greek as identifier, all langua
|
|||||||
|
|
||||||
Logical operation could be used the same
|
Logical operation could be used the same
|
||||||
|
|
||||||
val x = 10
|
var x = 10
|
||||||
++x >= 11
|
++x >= 11
|
||||||
>>> true
|
>>> true
|
||||||
|
|
||||||
@ -154,11 +162,18 @@ Correct pattern is:
|
|||||||
// now is OK:
|
// now is OK:
|
||||||
foo + bar
|
foo + bar
|
||||||
|
|
||||||
This is though a rare case when you need uninitialized variables, most often you can use conditional operatorss
|
This is though a rare case when you need uninitialized variables, most often you can use conditional operators
|
||||||
and even loops to assign results (see below).
|
and even loops to assign results (see below).
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
|
|
||||||
|
Almost the same, using `val`:
|
||||||
|
|
||||||
|
val foo = 1
|
||||||
|
foo += 1 // this will throw exception
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
|
||||||
Same as in kotlin:
|
Same as in kotlin:
|
||||||
|
|
||||||
val HalfPi = π / 2
|
val HalfPi = π / 2
|
||||||
|
@ -86,7 +86,7 @@ class Compiler(
|
|||||||
|
|
||||||
private fun parseExpression(tokens: CompilerContext): Statement? {
|
private fun parseExpression(tokens: CompilerContext): Statement? {
|
||||||
val pos = tokens.currentPos()
|
val pos = tokens.currentPos()
|
||||||
return parseExpressionLevel(tokens)?.let { a -> statement(pos) { a.getter(it) } }
|
return parseExpressionLevel(tokens)?.let { a -> statement(pos) { a.getter(it).value } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseExpressionLevel(tokens: CompilerContext, level: Int = 0): Accessor? {
|
private fun parseExpressionLevel(tokens: CompilerContext, level: Int = 0): Accessor? {
|
||||||
@ -185,9 +185,9 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NOT -> {
|
Token.Type.NOT -> {
|
||||||
if( operand != null ) throw ScriptError(t.pos, "unexpected operator not '!'")
|
if (operand != null) throw ScriptError(t.pos, "unexpected operator not '!'")
|
||||||
val op = parseTerm3(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
val op = parseTerm3(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||||
operand = Accessor { op.getter(it).logicalNot(it) }
|
operand = Accessor { op.getter(it).value.logicalNot(it).asReadonly }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.DOT -> {
|
Token.Type.DOT -> {
|
||||||
@ -202,18 +202,20 @@ class Compiler(
|
|||||||
isCall = true
|
isCall = true
|
||||||
operand = Accessor { context ->
|
operand = Accessor { context ->
|
||||||
context.pos = next.pos
|
context.pos = next.pos
|
||||||
val v = left.getter(context)
|
val v = left.getter(context).value
|
||||||
v.callInstanceMethod(
|
WithAccess(
|
||||||
context,
|
v.callInstanceMethod(
|
||||||
next.value,
|
context,
|
||||||
args.toArguments()
|
next.value,
|
||||||
|
args.toArguments()
|
||||||
|
), isMutable = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isCall) {
|
if (!isCall) {
|
||||||
operand = Accessor { context ->
|
operand = Accessor { context ->
|
||||||
left.getter(context).readField(context, next.value)
|
left.getter(context).value.readField(context, next.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
|
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
|
||||||
@ -234,7 +236,7 @@ class Compiler(
|
|||||||
// Expression in parentheses
|
// Expression in parentheses
|
||||||
val statement = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
val statement = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||||
operand = Accessor {
|
operand = Accessor {
|
||||||
statement.execute(it)
|
statement.execute(it).asReadonly
|
||||||
}
|
}
|
||||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'")
|
cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'")
|
||||||
@ -248,7 +250,7 @@ class Compiler(
|
|||||||
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
||||||
cc.previous()
|
cc.previous()
|
||||||
val s = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting valid statement")
|
val s = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting valid statement")
|
||||||
operand = Accessor { s.execute(it) }
|
operand = Accessor { s.execute(it).asReadonly }
|
||||||
}
|
}
|
||||||
|
|
||||||
"else", "break", "continue" -> {
|
"else", "break", "continue" -> {
|
||||||
@ -263,10 +265,10 @@ class Compiler(
|
|||||||
// is RW:
|
// is RW:
|
||||||
operand = Accessor({
|
operand = Accessor({
|
||||||
it.pos = t.pos
|
it.pos = t.pos
|
||||||
left.getter(it).readField(it, t.value)
|
left.getter(it).value.readField(it, t.value)
|
||||||
}) { cxt, newValue ->
|
}) { cxt, newValue ->
|
||||||
cxt.pos = t.pos
|
cxt.pos = t.pos
|
||||||
left.getter(cxt).writeField(cxt, t.value, newValue)
|
left.getter(cxt).value.writeField(cxt, t.value, newValue)
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// variable to read or like
|
// variable to read or like
|
||||||
@ -284,13 +286,20 @@ class Compiler(
|
|||||||
operand?.let { left ->
|
operand?.let { left ->
|
||||||
// post increment
|
// post increment
|
||||||
left.setter(startPos)
|
left.setter(startPos)
|
||||||
operand = Accessor({ ctx ->
|
operand = Accessor({ cxt ->
|
||||||
left.getter(ctx).getAndIncrement(ctx)
|
val x = left.getter(cxt)
|
||||||
|
if (x.isMutable)
|
||||||
|
x.value.getAndIncrement(cxt).asReadonly
|
||||||
|
else cxt.raiseError("Cannot increment immutable value")
|
||||||
})
|
})
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// no lvalue means pre-increment, expression to increment follows
|
// no lvalue means pre-increment, expression to increment follows
|
||||||
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||||
operand = Accessor({ ctx -> next.getter(ctx).incrementAndGet(ctx) })
|
operand = Accessor({ ctx ->
|
||||||
|
next.getter(ctx).also {
|
||||||
|
if (!it.isMutable) ctx.raiseError("Cannot increment immutable value")
|
||||||
|
}.value.incrementAndGet(ctx).asReadonly
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,12 +309,18 @@ class Compiler(
|
|||||||
// post decrement
|
// post decrement
|
||||||
left.setter(startPos)
|
left.setter(startPos)
|
||||||
operand = Accessor { ctx ->
|
operand = Accessor { ctx ->
|
||||||
left.getter(ctx).getAndDecrement(ctx)
|
left.getter(ctx).also {
|
||||||
|
if (!it.isMutable) ctx.raiseError("Cannot decrement immutable value")
|
||||||
|
}.value.getAndDecrement(ctx).asReadonly
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// no lvalue means pre-decrement, expression to decrement follows
|
// no lvalue means pre-decrement, expression to decrement follows
|
||||||
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||||
operand = Accessor { ctx -> next.getter(ctx).decrementAndGet(ctx) }
|
operand = Accessor { ctx ->
|
||||||
|
next.getter(ctx).also {
|
||||||
|
if (!it.isMutable) ctx.raiseError("Cannot decrement immutable value")
|
||||||
|
}.value.decrementAndGet(ctx).asReadonly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -327,7 +342,7 @@ class Compiler(
|
|||||||
if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expecting ID after ::")
|
if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expecting ID after ::")
|
||||||
return when (t.value) {
|
return when (t.value) {
|
||||||
"class" -> Accessor {
|
"class" -> Accessor {
|
||||||
operand.getter(it).objClass
|
operand.getter(it).value.objClass.asReadonly
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw ScriptError(t.pos, "Unknown scope operation: ${t.value}")
|
else -> throw ScriptError(t.pos, "Unknown scope operation: ${t.value}")
|
||||||
@ -357,13 +372,13 @@ class Compiler(
|
|||||||
|
|
||||||
return Accessor { context ->
|
return Accessor { context ->
|
||||||
val v = left.getter(context)
|
val v = left.getter(context)
|
||||||
v.callOn(context.copy(
|
v.value.callOn(context.copy(
|
||||||
context.pos,
|
context.pos,
|
||||||
Arguments(
|
Arguments(
|
||||||
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,31 +389,31 @@ class Compiler(
|
|||||||
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
val n = parseNumber(true, cc)
|
val n = parseNumber(true, cc)
|
||||||
Accessor({ n })
|
Accessor{ n.asReadonly }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.STRING -> Accessor({ ObjString(t.value) })
|
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
|
||||||
|
|
||||||
Token.Type.PLUS -> {
|
Token.Type.PLUS -> {
|
||||||
val n = parseNumber(true, cc)
|
val n = parseNumber(true, cc)
|
||||||
Accessor { n }
|
Accessor { n.asReadonly }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.MINUS -> {
|
Token.Type.MINUS -> {
|
||||||
val n = parseNumber(false, cc)
|
val n = parseNumber(false, cc)
|
||||||
Accessor { n }
|
Accessor { n.asReadonly }
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
when (t.value) {
|
when (t.value) {
|
||||||
"void" -> Accessor { ObjVoid }
|
"void" -> Accessor { ObjVoid.asReadonly }
|
||||||
"null" -> Accessor { ObjNull }
|
"null" -> Accessor { ObjNull.asReadonly }
|
||||||
"true" -> Accessor { ObjBool(true) }
|
"true" -> Accessor { ObjBool(true).asReadonly }
|
||||||
"false" -> Accessor { ObjBool(false) }
|
"false" -> Accessor { ObjBool(false).asReadonly }
|
||||||
else -> {
|
else -> {
|
||||||
Accessor({
|
Accessor({
|
||||||
it.pos = t.pos
|
it.pos = t.pos
|
||||||
it.get(t.value)?.value
|
it.get(t.value)?.asAccess
|
||||||
?: it.raiseError("symbol not defined: '${t.value}'")
|
?: it.raiseError("symbol not defined: '${t.value}'")
|
||||||
}) { ctx, newValue ->
|
}) { ctx, newValue ->
|
||||||
ctx.get(t.value)?.let { stored ->
|
ctx.get(t.value)?.let { stored ->
|
||||||
@ -719,7 +734,7 @@ class Compiler(
|
|||||||
|
|
||||||
data class Operator(
|
data class Operator(
|
||||||
val tokenType: Token.Type,
|
val tokenType: Token.Type,
|
||||||
val priority: Int, val arity: Int=2,
|
val priority: Int, val arity: Int = 2,
|
||||||
val generate: (Pos, Accessor, Accessor) -> Accessor
|
val generate: (Pos, Accessor, Accessor) -> Accessor
|
||||||
) {
|
) {
|
||||||
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
|
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
|
||||||
@ -727,7 +742,7 @@ class Compiler(
|
|||||||
companion object {
|
companion object {
|
||||||
fun simple(tokenType: Token.Type, priority: Int, f: suspend (Context, Obj, Obj) -> Obj): Operator =
|
fun simple(tokenType: Token.Type, priority: Int, f: suspend (Context, Obj, Obj) -> Obj): Operator =
|
||||||
Operator(tokenType, priority, 2, { _: Pos, a: Accessor, b: Accessor ->
|
Operator(tokenType, priority, 2, { _: Pos, a: Accessor, b: Accessor ->
|
||||||
Accessor { f(it, a.getter(it), b.getter(it)) }
|
Accessor { f(it, a.getter(it).value, b.getter(it).value).asReadonly }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,73 +755,74 @@ class Compiler(
|
|||||||
// assignments
|
// assignments
|
||||||
Operator(Token.Type.ASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.ASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val value = b.getter(it)
|
val value = b.getter(it).value
|
||||||
a.setter(pos)(it, value)
|
val access = a.getter(it)
|
||||||
value
|
if (!access.isMutable) throw ScriptError(pos, "cannot assign to immutable variable")
|
||||||
|
if (access.value.assign(it, value) == null)
|
||||||
|
a.setter(pos)(it, value)
|
||||||
|
value.asReadonly
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator(Token.Type.PLUSASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.PLUSASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val x = a.getter(it)
|
val x = a.getter(it).value
|
||||||
val y = b.getter(it)
|
val y = b.getter(it).value
|
||||||
x.plusAssign(it, y) ?: run {
|
(x.plusAssign(it, y) ?: run {
|
||||||
val result = x.plus(it, y)
|
val result = x.plus(it, y)
|
||||||
a.setter(pos)(it, result)
|
a.setter(pos)(it, result)
|
||||||
result
|
result
|
||||||
}
|
}).asReadonly
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator(Token.Type.MINUSASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.MINUSASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val x = a.getter(it)
|
val x = a.getter(it).value
|
||||||
val y = b.getter(it)
|
val y = b.getter(it).value
|
||||||
x.minusAssign(it, y) ?: run {
|
(x.minusAssign(it, y) ?: run {
|
||||||
val result = x.minus(it, y)
|
val result = x.minus(it, y)
|
||||||
a.setter(pos)(it, result)
|
a.setter(pos)(it, result)
|
||||||
result
|
result
|
||||||
}
|
}).asReadonly
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator(Token.Type.STARASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.STARASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val x = a.getter(it)
|
val x = a.getter(it).value
|
||||||
val y = b.getter(it)
|
val y = b.getter(it).value
|
||||||
x.mulAssign(it, y) ?: run {
|
(x.mulAssign(it, y) ?: run {
|
||||||
val result = x.mul(it, y)
|
val result = x.mul(it, y)
|
||||||
a.setter(pos)(it, result)
|
a.setter(pos)(it, result)
|
||||||
result
|
result
|
||||||
|
|
||||||
}
|
}).asReadonly
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator(Token.Type.SLASHASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.SLASHASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val x = a.getter(it)
|
val x = a.getter(it).value
|
||||||
val y = b.getter(it)
|
val y = b.getter(it).value
|
||||||
x.divAssign(it, y) ?: run {
|
(x.divAssign(it, y) ?: run {
|
||||||
val result = x.div(it, y)
|
val result = x.div(it, y)
|
||||||
a.setter(pos)(it, result)
|
a.setter(pos)(it, result)
|
||||||
result
|
result
|
||||||
|
}).asReadonly
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator(Token.Type.PERCENTASSIGN, lastPrty) { pos, a, b ->
|
Operator(Token.Type.PERCENTASSIGN, lastPrty) { pos, a, b ->
|
||||||
Accessor {
|
Accessor {
|
||||||
val x = a.getter(it)
|
val x = a.getter(it).value
|
||||||
val y = b.getter(it)
|
val y = b.getter(it).value
|
||||||
x.modAssign(it, y) ?: run {
|
(x.modAssign(it, y) ?: run {
|
||||||
val result = x.mod(it, y)
|
val result = x.mod(it, y)
|
||||||
a.setter(pos)(it, result)
|
a.setter(pos)(it, result)
|
||||||
result
|
result
|
||||||
|
}).asReadonly
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// logical 1
|
// logical 1
|
||||||
Operator.simple(Token.Type.OR, ++lastPrty) { ctx, a, b -> a.logicalOr(ctx,b) },
|
Operator.simple(Token.Type.OR, ++lastPrty) { ctx, a, b -> a.logicalOr(ctx, b) },
|
||||||
// logical 2
|
// logical 2
|
||||||
Operator.simple(Token.Type.AND, ++lastPrty) { ctx, a, b -> a.logicalAnd(ctx,b) },
|
Operator.simple(Token.Type.AND, ++lastPrty) { ctx, a, b -> a.logicalAnd(ctx, b) },
|
||||||
// bitwise or 2
|
// bitwise or 2
|
||||||
// bitwise and 3
|
// bitwise and 3
|
||||||
// equality/ne 4
|
// equality/ne 4
|
||||||
@ -819,10 +835,10 @@ class Compiler(
|
|||||||
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
|
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
|
||||||
// shuttle <=> 6
|
// shuttle <=> 6
|
||||||
// bit shifts 7
|
// bit shifts 7
|
||||||
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx,b) },
|
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },
|
||||||
Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx,b) },
|
Operator.simple(Token.Type.MINUS, lastPrty) { ctx, a, b -> a.minus(ctx, b) },
|
||||||
|
|
||||||
Operator.simple(Token.Type.STAR, ++lastPrty) { ctx, a, b -> a.mul(ctx,b) },
|
Operator.simple(Token.Type.STAR, ++lastPrty) { ctx, a, b -> a.mul(ctx, b) },
|
||||||
Operator.simple(Token.Type.SLASH, lastPrty) { ctx, a, b -> a.div(ctx, b) },
|
Operator.simple(Token.Type.SLASH, lastPrty) { ctx, a, b -> a.div(ctx, b) },
|
||||||
Operator.simple(Token.Type.PERCENT, lastPrty) { ctx, a, b -> a.mod(ctx, b) },
|
Operator.simple(Token.Type.PERCENT, lastPrty) { ctx, a, b -> a.mod(ctx, b) },
|
||||||
)
|
)
|
||||||
|
@ -12,10 +12,10 @@ import kotlin.math.roundToLong
|
|||||||
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
||||||
|
|
||||||
data class Accessor(
|
data class Accessor(
|
||||||
val getter: suspend (Context) -> Obj,
|
val getter: suspend (Context) -> WithAccess<Obj>,
|
||||||
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
||||||
) {
|
) {
|
||||||
constructor(getter: suspend (Context) -> Obj) : this(getter, null)
|
constructor(getter: suspend (Context) -> WithAccess<Obj>) : this(getter, null)
|
||||||
|
|
||||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
||||||
}
|
}
|
||||||
@ -35,13 +35,13 @@ sealed class Obj {
|
|||||||
/**
|
/**
|
||||||
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
||||||
*/
|
*/
|
||||||
fun getInstanceMemberOrNull(name: String): Obj? {
|
fun getInstanceMemberOrNull(name: String): WithAccess<Obj>? {
|
||||||
members[name]?.let { return it.value }
|
members[name]?.let { return it }
|
||||||
parentInstances.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
parentInstances.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstanceMember(atPos: Pos, name: String): Obj =
|
fun getInstanceMember(atPos: Pos, name: String): WithAccess<Obj> =
|
||||||
getInstanceMemberOrNull(name)
|
getInstanceMemberOrNull(name)
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ sealed class Obj {
|
|||||||
// note that getInstanceMember traverses the hierarchy
|
// note that getInstanceMember traverses the hierarchy
|
||||||
// instance _methods_ are our ObjClass instance:
|
// instance _methods_ are our ObjClass instance:
|
||||||
// note that getInstanceMember traverses the hierarchy
|
// note that getInstanceMember traverses the hierarchy
|
||||||
objClass.getInstanceMember(context.pos, name).invoke(context, this, args)
|
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
||||||
|
|
||||||
// methods that to override
|
// methods that to override
|
||||||
|
|
||||||
@ -102,9 +102,7 @@ sealed class Obj {
|
|||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun assign(context: Context, other: Obj): Obj {
|
open suspend fun assign(context: Context, other: Obj): Obj? = null
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a += b
|
* a += b
|
||||||
@ -143,7 +141,7 @@ sealed class Obj {
|
|||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
fun readField(context: Context, name: String): Obj = getInstanceMember(context.pos, name)
|
fun readField(context: Context, name: String): WithAccess<Obj> = getInstanceMember(context.pos, name)
|
||||||
|
|
||||||
fun writeField(context: Context, name: String, newValue: Obj) {
|
fun writeField(context: Context, name: String, newValue: Obj) {
|
||||||
willMutate(context)
|
willMutate(context)
|
||||||
@ -152,7 +150,7 @@ sealed class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
|
fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
|
||||||
if (name in members || parentInstances.any<Obj> { name in it.members })
|
if (name in members || parentInstances.any { name in it.members })
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
members[name] = WithAccess(initialValue, isMutable)
|
members[name] = WithAccess(initialValue, isMutable)
|
||||||
}
|
}
|
||||||
@ -169,6 +167,9 @@ sealed class Obj {
|
|||||||
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
|
||||||
|
val asMutable: WithAccess<Obj> by lazy { WithAccess(this, true) }
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
inline fun <reified T> from(obj: T): Obj {
|
inline fun <reified T> from(obj: T): Obj {
|
||||||
|
@ -6,4 +6,6 @@ package net.sergeych.ling
|
|||||||
data class StoredObj(
|
data class StoredObj(
|
||||||
var value: Obj?,
|
var value: Obj?,
|
||||||
val isMutable: Boolean = false
|
val isMutable: Boolean = false
|
||||||
)
|
) {
|
||||||
|
val asAccess: WithAccess<Obj>? get() = value?.let { WithAccess(it, isMutable) }
|
||||||
|
}
|
@ -149,8 +149,9 @@ class ScriptTest {
|
|||||||
assertFailsWith<ScriptError> {
|
assertFailsWith<ScriptError> {
|
||||||
context.eval("a = 10")
|
context.eval("a = 10")
|
||||||
}
|
}
|
||||||
assertEquals(10, context.eval("b = a - 3 - 4; b").toInt())
|
assertEquals(17, context.eval("a").toInt())
|
||||||
assertEquals(10, context.eval("b").toInt())
|
assertEquals(5, context.eval("b = a - 7 - 5").toInt())
|
||||||
|
assertEquals(5, context.eval("b").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -632,5 +633,37 @@ class ScriptTest {
|
|||||||
assertEquals(2, ctx.eval("x %= 5").toInt())
|
assertEquals(2, ctx.eval("x %= 5").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testVals() = runTest {
|
||||||
|
val cxt = Context()
|
||||||
|
cxt.eval("val x = 11")
|
||||||
|
assertEquals(11, cxt.eval("x").toInt())
|
||||||
|
assertFails { cxt.eval("x = 12") }
|
||||||
|
assertFails { cxt.eval("x += 12") }
|
||||||
|
assertFails { cxt.eval("x -= 12") }
|
||||||
|
assertFails { cxt.eval("x *= 2") }
|
||||||
|
assertFails { cxt.eval("x /= 2") }
|
||||||
|
assertFails { cxt.eval("x++") }
|
||||||
|
assertFails { cxt.eval("++x") }
|
||||||
|
assertFails { cxt.eval("x--") }
|
||||||
|
assertFails { cxt.eval("--x") }
|
||||||
|
assertEquals(11, cxt.eval("x").toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun testMultiAssign() = runTest {
|
||||||
|
// assertEquals(
|
||||||
|
// 7,
|
||||||
|
// eval("""
|
||||||
|
// var x = 10
|
||||||
|
// var y = 2
|
||||||
|
// (x = 1) = 5
|
||||||
|
// println(x)
|
||||||
|
// println(y)
|
||||||
|
// x + y
|
||||||
|
// """.trimIndent()).toInt()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
}
|
}
|
@ -38,7 +38,7 @@ data class DocTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val detailedString by lazy {
|
val detailedString by lazy {
|
||||||
val codeWithLines = sourceLines.withIndex().map { (i, s) -> "${i + line}: $s" }.joinToString("\n")
|
val codeWithLines = sourceLines.withIndex().map { (i, s) -> "${i + line + 1}: $s" }.joinToString("\n")
|
||||||
var result = "$this\n$codeWithLines\n"
|
var result = "$this\n$codeWithLines\n"
|
||||||
if (expectedOutput.isNotBlank())
|
if (expectedOutput.isNotBlank())
|
||||||
result += "--------expected output--------\n$expectedOutput\n"
|
result += "--------expected output--------\n$expectedOutput\n"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user