diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index c8d82eb..231ce45 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -110,7 +110,7 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) */ data class Item( val name: String, - val type: TypeDecl = TypeDecl.Obj, + val type: TypeDecl = TypeDecl.TypeAny, val pos: Pos = Pos.builtIn, val isEllipsis: Boolean = false, /** diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index bd71577..9a2ed5f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -227,7 +227,7 @@ class Compiler( operand = Accessor({ cxt -> val i = index.execute(cxt) val x = left.getter(cxt).value - if( x == ObjNull && isOptional) ObjNull.asReadonly + if (x == ObjNull && isOptional) ObjNull.asReadonly else x.getAt(cxt, i).asMutable }) { cxt, newValue -> val i = (index.execute(cxt) as? ObjInt)?.value?.toInt() @@ -394,7 +394,7 @@ class Compiler( val callStatement = statement { // and the source closure of the lambda which might have other thisObj. - val context = AppliedContext(closure!!,args,this) + val context = AppliedContext(closure!!, args, this) if (argsDeclaration == null) { // no args: automatic var 'it' val l = args.list @@ -560,11 +560,11 @@ class Compiler( } private fun parseTypeDeclaration(cc: CompilerContext): TypeDecl { - val result = TypeDecl.Obj - cc.ifNextIs(Token.Type.COLON) { - TODO("parse type declaration here") - } - return result + return if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { + val tt = cc.requireToken(Token.Type.ID, "type name or type expression required") + val isNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true) + TypeDecl.Simple(tt.value, isNullable) + } else TypeDecl.TypeAny } /** @@ -590,6 +590,8 @@ class Compiler( cc.previous() parseExpression(cc)?.let { args += ParsedArgument(it, t.pos) } ?: throw ScriptError(t.pos, "Expecting arguments list") + if (cc.current().type == Token.Type.COLON) + parseTypeDeclaration(cc) // Here should be a valid termination: } } @@ -724,7 +726,7 @@ class Compiler( } /** - * Parse keyword-starting statenment. + * Parse keyword-starting statement. * @return parsed statement or null if, for example. [id] is not among keywords */ private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) { @@ -735,7 +737,6 @@ class Compiler( "for" -> parseForStatement(cc) "break" -> parseBreakStatement(id.pos, cc) "continue" -> parseContinueStatement(id.pos, cc) - "fn", "fun" -> parseFunctionDeclaration(cc) "if" -> parseIfStatement(cc) "class" -> parseClassDeclaration(cc, false) "try" -> parseTryStatement(cc) @@ -744,11 +745,16 @@ class Compiler( else -> { // triples cc.previous() + val isExtern = cc.skipId("extern") when { - cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private) - cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private) - cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true) - cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true) + cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern) + cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern) + cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true, isExtern = isExtern) + cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true, isExtern = isExtern) + + cc.matchQualifiers("fun") -> parseFunctionDeclaration(cc, isOpen = false, isExtern = isExtern) + cc.matchQualifiers("fn") -> parseFunctionDeclaration(cc, isOpen = false, isExtern = isExtern) + cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private, cc) cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private, cc) cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, cc, true) @@ -1419,29 +1425,35 @@ class Compiler( } private fun parseFunctionDeclaration( - tokens: CompilerContext, + cc: CompilerContext, visibility: Visibility = Visibility.Public, - @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false + @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false, + isExtern: Boolean = false ): Statement { - var t = tokens.next() + var t = cc.next() val start = t.pos val name = if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expected identifier after 'fn'") else t.value - t = tokens.next() + t = cc.next() if (t.type != Token.Type.LPAREN) throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'") - val argsDeclaration = parseArgsDeclaration(tokens) + val argsDeclaration = parseArgsDeclaration(cc) if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN) throw ScriptError( t.pos, "Bad function definition: expected valid argument declaration or () after 'fn ${name}'" ) + if (cc.current().type == Token.Type.COLON) parseTypeDeclaration(cc) + // Here we should be at open body - val fnStatements = parseBlock(tokens) + val fnStatements = if (isExtern) + statement { raiseError("extern function not provided: $name") } + else + parseBlock(cc) var closure: Context? = null @@ -1617,7 +1629,7 @@ class Compiler( // bitwise or 2 // bitwise and 3 // equality/ne 4 - Operator.simple(Token.Type.EQARROW, ++lastPrty) { c, a, b -> ObjMapEntry(a,b) }, + Operator.simple(Token.Type.EQARROW, ++lastPrty) { c, a, b -> ObjMapEntry(a, b) }, // Operator.simple(Token.Type.EQ, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) == 0) }, Operator.simple(Token.Type.NEQ, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) }, @@ -1634,7 +1646,7 @@ class Compiler( Operator.simple(Token.Type.IS, lastPrty) { c, a, b -> ObjBool(a.isInstanceOf(b)) }, Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) }, - Operator.simple(Token.Type.ELVIS, ++lastPrty) { c, a, b -> if( a == ObjNull) b else a }, + Operator.simple(Token.Type.ELVIS, ++lastPrty) { c, a, b -> if (a == ObjNull) b else a }, // shuttle <=> 6 Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b -> diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt index 94c3d00..fac4169 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt @@ -53,6 +53,20 @@ internal class CompilerContext(val tokens: List) { fun currentPos(): Pos = tokens[currentIndex].pos + /** + * If the next token is identifier `name`, skip it and return `true`. + * else leave where it is and return `false` + */ + fun skipId(name: String): Boolean { + current().let { t -> + if( t.type == Token.Type.ID && t.value == name ) { + next() + return true + } + } + return false + } + /** * Skips next token if its type is `tokenType`, returns `true` if so. * @param errorMessage message to throw if next token is not `tokenType` @@ -96,7 +110,6 @@ internal class CompilerContext(val tokens: List) { } } - @Suppress("NOTHING_TO_INLINE") inline fun addBreak() { breakFound = true } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt index 6da5640..9e3ccce 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt @@ -1,9 +1,14 @@ +@file:Suppress("unused") + package net.sergeych.lyng // this is highly experimental and subject to complete redesign // very soon -sealed class TypeDecl { +sealed class TypeDecl(val isNullable:Boolean = false) { // ?? // data class Fn(val argTypes: List, val retType: TypeDecl) : TypeDecl() - object Obj : TypeDecl() + object TypeAny : TypeDecl() + object TypeNullableAny : TypeDecl(true) + + class Simple(val name: String,isNullable: Boolean) : TypeDecl(isNullable) } diff --git a/lynglib/src/commonTest/kotlin/TypesTest.kt b/lynglib/src/commonTest/kotlin/TypesTest.kt index f0140bc..5c37f36 100644 --- a/lynglib/src/commonTest/kotlin/TypesTest.kt +++ b/lynglib/src/commonTest/kotlin/TypesTest.kt @@ -1,10 +1,53 @@ import kotlinx.coroutines.test.runTest +import net.sergeych.lyng.eval import kotlin.test.Test class TypesTest { @Test fun testTypeCollection1() = runTest { + eval(""" + class Point(x: Real, y: Real) + assert(Point(1,2).x == 1) + assert(Point(1,2).y == 2) + assert(Point(1,2) is Point) + """.trimIndent()) } + @Test + fun testTypeCollection2() = runTest { + eval(""" + fun fn1(x: Real, y: Real): Real { x + y } + """.trimIndent()) + + } + @Test + fun testTypeCollection3() = runTest { + eval(""" + class Test(a: Int) { + fun fn1(x: Real, y: Real): Real { x + y } + } + """.trimIndent()) + } + + @Test + fun testExternDeclarations() = runTest { + eval(""" + extern fun foo1(a: String): Void + assertThrows { foo1("1") } + class Test(a: Int) { + extern fun fn1(x: Real, y: Real): Real +// extern val b: Int + } +// println("1") + val t = Test(0) +// println(t.b) +// println("2") + assertThrows { + t.fn1(1,2) + } +// println("4") + + """.trimIndent()) + } } \ No newline at end of file