more on classes
universal args declaration parser, lambda code rewritten to it
This commit is contained in:
parent
4dc73b91c2
commit
71a2933066
@ -0,0 +1,98 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* List of argument declarations in the __definition__ of the lambda, class constructor,
|
||||
* function, etc. It is created by [Compiler.parseArgsDeclaration]
|
||||
*/
|
||||
data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
|
||||
init {
|
||||
val i = args.count { it.isEllipsis }
|
||||
if (i > 1) throw ScriptError(args[i].pos, "there can be only one argument")
|
||||
}
|
||||
|
||||
/**
|
||||
* parse args and create local vars in a given context
|
||||
*/
|
||||
suspend fun assignToContext(
|
||||
context: Context,
|
||||
fromArgs: Arguments = context.args,
|
||||
defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var
|
||||
) {
|
||||
fun assign(a: Item, value: Obj) {
|
||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value)
|
||||
}
|
||||
|
||||
suspend fun processHead(index: Int): Int {
|
||||
var i = index
|
||||
while (i != args.size) {
|
||||
val a = args[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
i < fromArgs.size -> fromArgs[i]
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> context.raiseArgumentError("too few arguments for the call")
|
||||
}
|
||||
assign(a, value)
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
suspend fun processTail(index: Int): Int {
|
||||
var i = args.size - 1
|
||||
var j = fromArgs.size - 1
|
||||
while (i > index) {
|
||||
val a = args[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
j >= index -> {
|
||||
fromArgs[j--]
|
||||
}
|
||||
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> context.raiseArgumentError("too few arguments for the call")
|
||||
}
|
||||
assign(a, value)
|
||||
i--
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
||||
val a = args[index]
|
||||
val l = if (index > toFromIndex) ObjList()
|
||||
else ObjList( fromArgs.values.subList(index, toFromIndex+1).toMutableList())
|
||||
assign(a, l)
|
||||
}
|
||||
|
||||
val leftIndex = processHead(0)
|
||||
if (leftIndex < args.size) {
|
||||
val end = processTail(leftIndex)
|
||||
processEllipsis(leftIndex, end)
|
||||
}
|
||||
else {
|
||||
if( leftIndex < fromArgs.size)
|
||||
context.raiseArgumentError("too many arguments for the call")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single argument declaration descriptor.
|
||||
*
|
||||
* @param defaultValue default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
||||
* If not null, could be executed on __caller context__ only.
|
||||
*/
|
||||
data class Item(
|
||||
val name: String,
|
||||
val type: TypeDecl = TypeDecl.Obj,
|
||||
val pos: Pos = Pos.builtIn,
|
||||
val isEllipsis: Boolean = false,
|
||||
/**
|
||||
* Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
||||
* So it is a [Statement] that must be executed on __caller context__.
|
||||
*/
|
||||
val defaultValue: Statement? = null,
|
||||
val accessType: Compiler.AccessType? = null,
|
||||
val visibility: Compiler.Visibility? = null,
|
||||
)
|
||||
}
|
@ -33,6 +33,7 @@ data class Arguments(val list: List<Info>) : Iterable<Obj> {
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
fun from(values: Collection<Obj>) = Arguments(values.map { Info(it, Pos.UNKNOWN) })
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Obj> {
|
||||
|
@ -347,22 +347,8 @@ class Compiler(
|
||||
}
|
||||
context.addItem("it", false, itValue)
|
||||
} else {
|
||||
// assign vars as declared
|
||||
if (args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis)
|
||||
raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}")
|
||||
for ((n, a) in argsDeclaration.args.withIndex()) {
|
||||
if (n >= args.size) {
|
||||
if (a.initialValue != null)
|
||||
context.addItem(a.name, false, a.initialValue.execute(context))
|
||||
else throw ScriptError(a.pos, "argument $n is out of scope")
|
||||
} else {
|
||||
val value = if (a.isEllipsis) {
|
||||
ObjList(args.values.subList(n, args.values.size).toMutableList())
|
||||
} else
|
||||
args[n]
|
||||
context.addItem(a.name, false, value)
|
||||
}
|
||||
}
|
||||
// assign vars as declared the standard way
|
||||
argsDeclaration.assignToContext(context)
|
||||
}
|
||||
body.execute(context)
|
||||
}
|
||||
@ -411,29 +397,12 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
enum class AccessType {
|
||||
Val, Var, Default
|
||||
enum class AccessType(val isMutable: Boolean) {
|
||||
Val(false), Var(true), Initialization(false)
|
||||
}
|
||||
|
||||
enum class Visibility {
|
||||
Default, Public, Private, Protected, Internal
|
||||
}
|
||||
|
||||
data class ArgVar(
|
||||
val name: String,
|
||||
val type: TypeDecl = TypeDecl.Obj,
|
||||
val pos: Pos,
|
||||
val isEllipsis: Boolean,
|
||||
val initialValue: Statement? = null,
|
||||
val accessType: AccessType = AccessType.Default,
|
||||
val visibility: Visibility = Visibility.Default
|
||||
)
|
||||
|
||||
data class ArgsDeclaration(val args: List<ArgVar>, val endTokenType: Token.Type) {
|
||||
init {
|
||||
val i = args.indexOfFirst { it.isEllipsis }
|
||||
if (i >= 0 && i != args.lastIndex) throw ScriptError(args[i].pos, "ellipsis argument must be last")
|
||||
}
|
||||
Public, Private, Protected, Internal
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,7 +410,7 @@ class Compiler(
|
||||
* @return declaration or null if there is no valid list of arguments
|
||||
*/
|
||||
private fun parseArgsDeclaration(cc: CompilerContext, isClassDeclaration: Boolean = false): ArgsDeclaration? {
|
||||
val result = mutableListOf<ArgVar>()
|
||||
val result = mutableListOf<ArgsDeclaration.Item>()
|
||||
var endTokenType: Token.Type? = null
|
||||
val startPos = cc.savePos()
|
||||
|
||||
@ -484,7 +453,7 @@ class Compiler(
|
||||
Visibility.Public
|
||||
}
|
||||
|
||||
else -> Visibility.Default
|
||||
else -> null
|
||||
}
|
||||
// val/var?
|
||||
val access = when (t.value) {
|
||||
@ -504,7 +473,7 @@ class Compiler(
|
||||
AccessType.Var
|
||||
}
|
||||
|
||||
else -> AccessType.Default
|
||||
else -> null
|
||||
}
|
||||
|
||||
var defaultValue: Statement? = null
|
||||
@ -514,7 +483,15 @@ class Compiler(
|
||||
// type information
|
||||
val typeInfo = parseTypeDeclaration(cc)
|
||||
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
||||
result += ArgVar(t.value, typeInfo, t.pos, isEllipsis, defaultValue, access, visibility)
|
||||
result += ArgsDeclaration.Item(
|
||||
t.value,
|
||||
typeInfo,
|
||||
t.pos,
|
||||
isEllipsis,
|
||||
defaultValue,
|
||||
access,
|
||||
visibility
|
||||
)
|
||||
|
||||
// important: valid argument list continues with ',' and ends with '->' or ')'
|
||||
// otherwise it is not an argument list:
|
||||
@ -714,24 +691,45 @@ class Compiler(
|
||||
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val parsedArgs = parseArgsDeclaration(cc)
|
||||
|
||||
if( parsedArgs != null && parsedArgs.endTokenType != Token.Type.RPAREN)
|
||||
throw ScriptError(nameToken.pos, "Bad class declaration: expected ')' at the end of the primary constructor")
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
val t = cc.next()
|
||||
if (t.type == Token.Type.LBRACE) {
|
||||
|
||||
var extraInit: Statement? = null
|
||||
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
||||
// parse body
|
||||
}
|
||||
TODO("parse body")
|
||||
} else null
|
||||
|
||||
// create class
|
||||
val className = nameToken.value
|
||||
lateinit var classContext: Context
|
||||
|
||||
// val constructorCode = statement {
|
||||
// val classContext = copy()
|
||||
// }
|
||||
val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization
|
||||
val defaultVisibility = Visibility.Public
|
||||
|
||||
// create instance constructor
|
||||
// create custom objClass with all fields and instance constructor
|
||||
|
||||
val newClass = ObjClass(className, parsedArgs?.args ?: emptyList())
|
||||
// statement {
|
||||
// addConst(nameToken.value, )
|
||||
// }
|
||||
val constructorCode = statement {
|
||||
// constructor code is registered with class instance and is called over
|
||||
// new `thisObj` already set by class to ObjInstance
|
||||
thisObj as ObjInstance
|
||||
// the context now is a "class creation context", we must use its args to initialize
|
||||
// fields. Note that 'this' is already set by class
|
||||
// parsedArgs?.let { pa ->
|
||||
// pa.extractArgs { (def, value) ->
|
||||
// val access = def.accessType ?: defaultAccess
|
||||
// val visibility = def.visibility ?: defaultVisibility
|
||||
// addItem(def.name, access.isMutable, value)
|
||||
// }
|
||||
//
|
||||
thisObj
|
||||
|
||||
}
|
||||
TODO()
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,9 @@ class Context(
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY,newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
|
||||
fun copy(args: Arguments = Arguments.EMPTY,newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
|
||||
fun copy() = Context(this, args, pos, thisObj)
|
||||
|
||||
fun addItem(name: String, isMutable: Boolean, value: Obj?): StoredObj {
|
||||
|
@ -4,7 +4,7 @@ val ObjClassType by lazy { ObjClass("Class") }
|
||||
|
||||
class ObjClass(
|
||||
val className: String,
|
||||
val constructorArgs: List<Compiler.ArgVar> = emptyList(),
|
||||
val constructorArgs: List<ArgsDeclaration.Item> = emptyList(),
|
||||
vararg val parents: ObjClass,
|
||||
) : Obj() {
|
||||
constructor(
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
|
||||
init {
|
||||
for (p in objClass.parents)
|
||||
|
@ -16,6 +16,7 @@ data class Pos(val source: Source, val line: Int, val column: Int) {
|
||||
|
||||
companion object {
|
||||
val builtIn = Pos(Source.builtIn, 0, 0)
|
||||
val UNKNOWN = Pos(Source.UNKNOWN, -1, -1)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -119,6 +119,13 @@ class Script(
|
||||
raiseError(ObjAssertionError(this,"Assertion failed"))
|
||||
}
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) != 0 )
|
||||
raiseError(ObjAssertionError(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ class Source(val fileName: String, text: String) {
|
||||
|
||||
companion object {
|
||||
val builtIn: Source by lazy { Source("built-in", "") }
|
||||
val UNKNOWN: Source by lazy { Source("UNKNOWN", "") }
|
||||
}
|
||||
|
||||
val startPos: Pos = Pos(this, 0, 0)
|
||||
|
@ -486,6 +486,136 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssignArgumentsNoEllipsis() = runTest {
|
||||
// equal args, no ellipsis, no defaults, ok
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
var pa = ArgsDeclaration(listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
var c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
assertEquals( ObjInt(1), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(3), c["c"]?.value)
|
||||
// less args: error
|
||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1,2).map { it.toObj() }))
|
||||
assertFailsWith<ScriptError> {
|
||||
pa.assignToContext(c)
|
||||
}
|
||||
// less args, no ellipsis, defaults, ok
|
||||
pa = ArgsDeclaration(listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c", defaultValue = statement { ObjInt(100) }),
|
||||
), ttEnd)
|
||||
pa.assignToContext(c)
|
||||
assertEquals( ObjInt(1), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(100), c["c"]?.value)
|
||||
// enough args. default value is ignored:
|
||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
assertEquals( ObjInt(10), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(5), c["c"]?.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssignArgumentsEndEllipsis() = runTest {
|
||||
// equal args,
|
||||
// less args, no ellipsis, defaults, ok
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
var pa = ArgsDeclaration(listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b", isEllipsis = true),
|
||||
), ttEnd)
|
||||
var c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assert( a == 1 ); println(b)")
|
||||
c.eval("assert( b == [2,3] )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(1,2).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( a, 1 ); println(b)")
|
||||
c.eval("assertEquals( b, [2] )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(1).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assert( a == 1 ); println(b)")
|
||||
c.eval("assert( b == [] )")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssignArgumentsStartEllipsis() = runTest {
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
var pa = ArgsDeclaration(listOf(
|
||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
var c = Context(args = Arguments.from(listOf(0,1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( a,[0,1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( a,[1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( a,[] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(3).map { it.toObj() }))
|
||||
assertFailsWith<ExecutionError> {
|
||||
pa.assignToContext(c)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAssignArgumentsmiddleEllipsis() = runTest {
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
var pa = ArgsDeclaration(listOf(
|
||||
ArgsDeclaration.Item("i"),
|
||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
var c = Context(args = Arguments.from(listOf(-1,0,1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( i, -1 )")
|
||||
c.eval("assertEquals( a,[0,1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(0, 1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( i, 0 )")
|
||||
c.eval("assertEquals( a,[1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
c.eval("assertEquals( i, 1)")
|
||||
c.eval("assertEquals( a,[] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
c.eval("assertEquals( c, 3 )")
|
||||
|
||||
c = Context(args = Arguments.from(listOf(2,3).map { it.toObj() }))
|
||||
assertFailsWith<ExecutionError> {
|
||||
pa.assignToContext(c)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWhileBlockIsolation1() = runTest {
|
||||
eval(
|
||||
|
Loading…
x
Reference in New Issue
Block a user