Compare commits

...

3 Commits

9 changed files with 129 additions and 42 deletions

View File

@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.6.7-SNAPSHOT" version = "0.6.8-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -0,0 +1,17 @@
package net.sergeych.lyng
/**
* Special version of the [Context] used to `apply` new this object to
* _parent context property.
*
* @param _parent context to apply to
* @param args arguments for the new context
* @param appliedContext the new context to apply, it will have lower priority except for `this` which
* will be reset by appliedContext's `this`.
*/
class AppliedContext(_parent: Context, args: Arguments, val appliedContext: Context)
: Context(_parent, args, appliedContext.pos, appliedContext.thisObj) {
override fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly
else super.get(name) ?: appliedContext[name]
}

View File

@ -110,7 +110,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
*/ */
data class Item( data class Item(
val name: String, val name: String,
val type: TypeDecl = TypeDecl.Obj, val type: TypeDecl = TypeDecl.TypeAny,
val pos: Pos = Pos.builtIn, val pos: Pos = Pos.builtIn,
val isEllipsis: Boolean = false, val isEllipsis: Boolean = false,
/** /**

View File

@ -227,7 +227,7 @@ class Compiler(
operand = Accessor({ cxt -> operand = Accessor({ cxt ->
val i = index.execute(cxt) val i = index.execute(cxt)
val x = left.getter(cxt).value val x = left.getter(cxt).value
if( x == ObjNull && isOptional) ObjNull.asReadonly if (x == ObjNull && isOptional) ObjNull.asReadonly
else x.getAt(cxt, i).asMutable else x.getAt(cxt, i).asMutable
}) { cxt, newValue -> }) { cxt, newValue ->
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt() val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
@ -387,14 +387,14 @@ class Compiler(
val argsDeclaration = parseArgsDeclaration(cc) val argsDeclaration = parseArgsDeclaration(cc)
if (argsDeclaration != null && argsDeclaration.endTokenType != Token.Type.ARROW) if (argsDeclaration != null && argsDeclaration.endTokenType != Token.Type.ARROW)
throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments") throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments")
val pos = cc.currentPos()
val body = parseBlock(cc, skipLeadingBrace = true) val body = parseBlock(cc, skipLeadingBrace = true)
var closure: Context? = null var closure: Context? = null
val callStatement = statement { val callStatement = statement {
// and the source closure of the lambda which might have other thisObj. // and the source closure of the lambda which might have other thisObj.
val context = closure!!.copy(pos, args).applyContext(this) val context = AppliedContext(closure!!, args, this)
if (argsDeclaration == null) { if (argsDeclaration == null) {
// no args: automatic var 'it' // no args: automatic var 'it'
val l = args.list val l = args.list
@ -560,11 +560,11 @@ class Compiler(
} }
private fun parseTypeDeclaration(cc: CompilerContext): TypeDecl { private fun parseTypeDeclaration(cc: CompilerContext): TypeDecl {
val result = TypeDecl.Obj return if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
cc.ifNextIs(Token.Type.COLON) { val tt = cc.requireToken(Token.Type.ID, "type name or type expression required")
TODO("parse type declaration here") val isNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
} TypeDecl.Simple(tt.value, isNullable)
return result } else TypeDecl.TypeAny
} }
/** /**
@ -590,6 +590,8 @@ class Compiler(
cc.previous() cc.previous()
parseExpression(cc)?.let { args += ParsedArgument(it, t.pos) } parseExpression(cc)?.let { args += ParsedArgument(it, t.pos) }
?: throw ScriptError(t.pos, "Expecting arguments list") ?: throw ScriptError(t.pos, "Expecting arguments list")
if (cc.current().type == Token.Type.COLON)
parseTypeDeclaration(cc)
// Here should be a valid termination: // 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 * @return parsed statement or null if, for example. [id] is not among keywords
*/ */
private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) { private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) {
@ -735,7 +737,6 @@ class Compiler(
"for" -> parseForStatement(cc) "for" -> parseForStatement(cc)
"break" -> parseBreakStatement(id.pos, cc) "break" -> parseBreakStatement(id.pos, cc)
"continue" -> parseContinueStatement(id.pos, cc) "continue" -> parseContinueStatement(id.pos, cc)
"fn", "fun" -> parseFunctionDeclaration(cc)
"if" -> parseIfStatement(cc) "if" -> parseIfStatement(cc)
"class" -> parseClassDeclaration(cc, false) "class" -> parseClassDeclaration(cc, false)
"try" -> parseTryStatement(cc) "try" -> parseTryStatement(cc)
@ -744,11 +745,16 @@ class Compiler(
else -> { else -> {
// triples // triples
cc.previous() cc.previous()
val isExtern = cc.skipId("extern")
when { when {
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private) cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern)
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private) cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern)
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true) cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true, isExtern = isExtern)
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true) 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("val", "private") -> parseVarDeclaration(false, Visibility.Private, cc)
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private, cc) cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private, cc)
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, cc, true) cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, cc, true)
@ -1419,29 +1425,35 @@ class Compiler(
} }
private fun parseFunctionDeclaration( private fun parseFunctionDeclaration(
tokens: CompilerContext, cc: CompilerContext,
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
isExtern: Boolean = false
): Statement { ): Statement {
var t = tokens.next() var t = cc.next()
val start = t.pos val start = t.pos
val name = if (t.type != Token.Type.ID) val name = if (t.type != Token.Type.ID)
throw ScriptError(t.pos, "Expected identifier after 'fn'") throw ScriptError(t.pos, "Expected identifier after 'fn'")
else t.value else t.value
t = tokens.next() t = cc.next()
if (t.type != Token.Type.LPAREN) if (t.type != Token.Type.LPAREN)
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'") 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) if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError( throw ScriptError(
t.pos, t.pos,
"Bad function definition: expected valid argument declaration or () after 'fn ${name}'" "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 // 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 var closure: Context? = null
@ -1617,7 +1629,7 @@ class Compiler(
// bitwise or 2 // bitwise or 2
// bitwise and 3 // bitwise and 3
// equality/ne 4 // 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.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) }, 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.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.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 // shuttle <=> 6
Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b -> Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b ->

View File

@ -53,6 +53,20 @@ internal class CompilerContext(val tokens: List<Token>) {
fun currentPos(): Pos = tokens[currentIndex].pos 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. * Skips next token if its type is `tokenType`, returns `true` if so.
* @param errorMessage message to throw if next token is not `tokenType` * @param errorMessage message to throw if next token is not `tokenType`
@ -96,7 +110,6 @@ internal class CompilerContext(val tokens: List<Token>) {
} }
} }
@Suppress("NOTHING_TO_INLINE")
inline fun addBreak() { inline fun addBreak() {
breakFound = true breakFound = true
} }

View File

@ -1,6 +1,6 @@
package net.sergeych.lyng package net.sergeych.lyng
class Context( open class Context(
val parent: Context?, val parent: Context?,
val args: Arguments = Arguments.EMPTY, val args: Arguments = Arguments.EMPTY,
var pos: Pos = Pos.builtIn, var pos: Pos = Pos.builtIn,
@ -13,15 +13,6 @@ class Context(
) )
: this(Script.defaultContext, args, pos) : this(Script.defaultContext, args, pos)
/**
* Making this context priority one
*/
fun applyContext(other: Context): Context {
if (other.thisObj != ObjVoid) thisObj = other.thisObj
appliedContext = other
return this
}
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented") fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
@Suppress("unused") @Suppress("unused")
@ -74,15 +65,13 @@ class Context(
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T) inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}") ?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
internal var appliedContext: Context? = null
internal val objects = mutableMapOf<String, ObjRecord>() internal val objects = mutableMapOf<String, ObjRecord>()
operator fun get(name: String): ObjRecord? = open operator fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly if (name == "this") thisObj.asReadonly
else { else {
objects[name] objects[name]
?: parent?.get(name) ?: parent?.get(name)
?: appliedContext?.get(name)
} }
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context = fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
@ -137,5 +126,4 @@ class Context(
fun containsLocal(name: String): Boolean = name in objects fun containsLocal(name: String): Boolean = name in objects
} }

View File

@ -1,9 +1,14 @@
@file:Suppress("unused")
package net.sergeych.lyng package net.sergeych.lyng
// this is highly experimental and subject to complete redesign // this is highly experimental and subject to complete redesign
// very soon // very soon
sealed class TypeDecl { sealed class TypeDecl(val isNullable:Boolean = false) {
// ?? // ??
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl() // data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
object Obj : TypeDecl() object TypeAny : TypeDecl()
object TypeNullableAny : TypeDecl(true)
class Simple(val name: String,isNullable: Boolean) : TypeDecl(isNullable)
} }

View File

@ -1,4 +1,3 @@
package io.github.kotlin.fibonacci
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.* import net.sergeych.lyng.*

View File

@ -0,0 +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())
}
}