From dacdcd7faa5f4a690eeec1d00d940e0523b13d41 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 13 Jun 2025 10:31:32 +0400 Subject: [PATCH] ref #13 get rid pf tokens for private/protected, added open attribute parsing --- .../kotlin/net/sergeych/lyng/Compiler.kt | 131 +++++++++--------- .../net/sergeych/lyng/CompilerContext.kt | 31 +++-- .../kotlin/net/sergeych/lyng/Parser.kt | 24 ++-- .../kotlin/net/sergeych/lyng/Token.kt | 1 - library/src/commonTest/kotlin/ScriptTest.kt | 19 +++ 5 files changed, 121 insertions(+), 85 deletions(-) diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index b46757e..c4d65d8 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -39,13 +39,6 @@ class Compiler( } } - Token.Type.PRIVATE, Token.Type.PROTECTED -> { - if (cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) { - continue - } else - throw ScriptError(t.pos, "unexpected keyword ${t.value}") - } - Token.Type.PLUS2, Token.Type.MINUS2 -> { cc.previous() parseExpression(cc) @@ -149,7 +142,7 @@ class Compiler( v.invokeInstanceMethod( context, next.value, - args.toArguments(context,false) + args.toArguments(context, false) ), isMutable = false ) } @@ -157,7 +150,7 @@ class Compiler( Token.Type.LBRACE -> { // single lambda arg, like assertTrows { ... } - cc.next() + cc.next() isCall = true val lambda = parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here") @@ -170,7 +163,7 @@ class Compiler( v.invokeInstanceMethod( context, next.value, - Arguments(listOf(lambda),true) + Arguments(listOf(lambda), true) ), isMutable = false ) } @@ -458,17 +451,14 @@ class Compiler( } Token.Type.NEWLINE -> {} - Token.Type.PROTECTED, Token.Type.PRIVATE -> { - if (!isClassDeclaration) { - cc.restorePos(startPos); return null - } - } Token.Type.ID -> { // visibility - val visibility = if (isClassDeclaration) - cc.getVisibility(Visibility.Public) - else Visibility.Public + val visibility = if( isClassDeclaration && t.value == "private" ) { + t = cc.next() + Visibility.Private + } else Visibility.Public + // val/var? val access = when (t.value) { "val" -> { @@ -707,8 +697,8 @@ class Compiler( * @return parsed statement or null if, for example. [id] is not among keywords */ private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) { - "val" -> parseVarDeclaration(id.value, false, cc) - "var" -> parseVarDeclaration(id.value, true, cc) + "val" -> parseVarDeclaration(false, Visibility.Public, cc) + "var" -> parseVarDeclaration(true, Visibility.Public, cc) "while" -> parseWhileStatement(cc) "do" -> parseDoWhileStatement(cc) "for" -> parseForStatement(cc) @@ -719,16 +709,33 @@ class Compiler( "class" -> parseClassDeclaration(cc, false) "try" -> parseTryStatement(cc) "throw" -> parseThrowStatement(cc) - else -> null + else -> { + // triples + cc.previous() + 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("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) + cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, cc, true) + else -> { + cc.next() + null + } + } + } } private fun parseThrowStatement(cc: CompilerContext): Statement { val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected") return statement { var errorObject = throwStatement.execute(this) - if( errorObject is ObjString ) + if (errorObject is ObjString) errorObject = ObjException(this, errorObject.value) - if( errorObject is ObjException ) + if (errorObject is ObjException) raiseError(errorObject) else raiseError("this is not an exception object: $errorObject") } @@ -745,7 +752,7 @@ class Compiler( val catches = mutableListOf() cc.skipTokens(Token.Type.NEWLINE) var t = cc.next() - while( t.value == "catch" ) { + while (t.value == "catch") { if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { t = cc.next() @@ -785,19 +792,21 @@ class Compiler( } else { // no (e: Exception) block: should be shortest variant `catch { ... }` cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here") - catches += CatchBlockData(Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"), - parseBlock(cc,true)) + catches += CatchBlockData( + Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"), + parseBlock(cc, true) + ) t = cc.next() } } - val finallyClause = if( t.value == "finally" ) { + val finallyClause = if (t.value == "finally") { parseBlock(cc) } else { cc.previous() null } - if( catches.isEmpty() && finallyClause == null ) + if (catches.isEmpty() && finallyClause == null) throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both") return statement { @@ -805,26 +814,25 @@ class Compiler( try { // body is a parsed block, it already has separate context result = body.execute(this) - } - catch (e: Exception) { + } catch (e: Exception) { // convert to appropriate exception - val objException = when(e) { + val objException = when (e) { is ExecutionError -> e.errorObject else -> ObjUnknownException(this, e.message ?: e.toString()) } // let's see if we should catch it: var isCaught = false - for( cdata in catches ) { + for (cdata in catches) { var exceptionObject: ObjException? = null for (exceptionClassName in cdata.classNames) { val exObj = ObjException.getErrorClass(exceptionClassName) ?: raiseSymbolNotFound("error clas not exists: $exceptionClassName") - if( objException.isInstanceOf(exObj) ) { + if (objException.isInstanceOf(exObj)) { exceptionObject = objException break } } - if( exceptionObject != null ) { + if (exceptionObject != null) { val catchContext = this.copy(pos = cdata.catchVar.pos) catchContext.addItem(cdata.catchVar.value, false, objException) result = cdata.block.execute(catchContext) @@ -833,10 +841,9 @@ class Compiler( } } // rethrow if not caught this exception - if( !isCaught ) + if (!isCaught) throw e - } - finally { + } finally { // finally clause does not alter result! finallyClause?.execute(this) } @@ -1081,8 +1088,8 @@ class Compiler( cc.skipTokens(Token.Type.NEWLINE) val t = cc.next() - if( t.type != Token.Type.ID && t.value != "while" ) - cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here") + if (t.type != Token.Type.ID && t.value != "while") + cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here") val conditionStart = ensureLparen(cc) val condition = @@ -1106,10 +1113,9 @@ class Compiler( doContext = it.copy().apply { skipContextCreation = true } try { result = body.execute(doContext) - } - catch( e: LoopBreakContinueException) { - if( e.label == label || e.label == null ) { - if( e.doContinue ) continue + } catch (e: LoopBreakContinueException) { + if (e.label == label || e.label == null) { + if (e.doContinue) continue else { result = e.result wasBroken = true @@ -1118,8 +1124,8 @@ class Compiler( } throw e } - } while( condition.execute(doContext).toBool() ) - if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) } + } while (condition.execute(doContext).toBool()) + if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } result } } @@ -1273,9 +1279,11 @@ class Compiler( } } - private fun parseFunctionDeclaration(tokens: CompilerContext): Statement { - val visibility = tokens.getVisibility() - + private fun parseFunctionDeclaration( + tokens: CompilerContext, + visibility: Visibility = Visibility.Public, + @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false + ): Statement { var t = tokens.next() val start = t.pos val name = if (t.type != Token.Type.ID) @@ -1329,7 +1337,7 @@ class Compiler( val block = parseScript(startPos, cc) return statement(startPos) { // block run on inner context: - block.execute(if( it.skipContextCreation ) it else it.copy(startPos)) + block.execute(if (it.skipContextCreation) it else it.copy(startPos)) }.also { val t1 = cc.next() if (t1.type != Token.Type.RBRACE) @@ -1337,26 +1345,22 @@ class Compiler( } } - private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement { - // we are just after var/val, visibility if exists is 2 steps behind - val visibility = when (tokens.atOffset(-2)?.type) { - Token.Type.PRIVATE -> - Visibility.Private - - Token.Type.PROTECTED -> Visibility.Protected - else -> Visibility.Public - } - + private fun parseVarDeclaration( + isMutable: Boolean, + visibility: Visibility, + tokens: CompilerContext, + @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false + ): Statement { val nameToken = tokens.next() val start = nameToken.pos if (nameToken.type != Token.Type.ID) - throw ScriptError(nameToken.pos, "Expected identifier after '$kind'") + throw ScriptError(nameToken.pos, "Expected identifier here") val name = nameToken.value val eqToken = tokens.next() var setNull = false if (eqToken.type != Token.Type.ASSIGN) { - if (!mutable) + if (!isMutable) throw ScriptError(start, "val must be initialized") else { tokens.previous() @@ -1375,7 +1379,7 @@ class Compiler( // create a separate copy: val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull - context.addItem(name, mutable, initValue, visibility) + context.addItem(name, isMutable, initValue, visibility) initValue } } @@ -1519,7 +1523,8 @@ class Compiler( /** * The keywords that stop processing of expression term */ - val stopKeywords = setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct") + val stopKeywords = + setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct") } } diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt index 47eabca..52ec1cf 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt @@ -102,6 +102,7 @@ internal class CompilerContext(val tokens: List) { * Return value of the next token if it is an identifier, null otherwise. * Does not change position. */ + @Suppress("unused") fun nextIdValue(): String? { return if (hasNext()) { val nt = tokens[currentIndex] @@ -117,21 +118,31 @@ internal class CompilerContext(val tokens: List) { /** * If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null. */ + @Suppress("unused") fun atOffset(offset: Int): Token? = if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null - /** - * Scan backwards as deep as specified looking for visibility token. Does not change position. - */ - fun getVisibility(default: Visibility = Visibility.Public, depths: Int = 2): Visibility { - for( i in -depths .. -1) { - when( atOffset(i)?.type) { - Token.Type.PROTECTED -> return Visibility.Protected - Token.Type.PRIVATE -> return Visibility.Private - else -> {} + fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean { + val pos = savePos() + var count = 0 + while( count < qualifiers.size) { + val t = next() + when(t.type) { + Token.Type.ID -> { + if( t.value in qualifiers ) count++ + else { restorePos(pos); return false } + } + Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {} + else -> { restorePos(pos); return false } } } - return default + val t = next() + if( t.type == Token.Type.ID && t.value == keyword ) { + return true + } else { + restorePos(pos) + return false + } } // fun expectKeyword(vararg keyword: String): String { diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt index 2af5e19..cee6d65 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt @@ -137,14 +137,18 @@ private class Parser(fromPos: Pos) { if (currentChar == '.') { pos.advance() // .. already parsed: - if (currentChar == '.') { - pos.advance() - Token("...", from, Token.Type.ELLIPSIS) - } else if (currentChar == '<') { - pos.advance() - Token("..<", from, Token.Type.DOTDOTLT) - } else { - Token("..", from, Token.Type.DOTDOT) + when (currentChar) { + '.' -> { + pos.advance() + Token("...", from, Token.Type.ELLIPSIS) + } + '<' -> { + pos.advance() + Token("..<", from, Token.Type.DOTDOTLT) + } + else -> { + Token("..", from, Token.Type.DOTDOT) + } } } else Token(".", from, Token.Type.DOT) @@ -280,8 +284,6 @@ private class Parser(fromPos: Pos) { when (text) { "in" -> Token("in", from, Token.Type.IN) "is" -> Token("is", from, Token.Type.IS) - "protected" -> Token("protected", from, Token.Type.PROTECTED) - "private" -> Token("private", from, Token.Type.PRIVATE) else -> Token(text, from, Token.Type.ID) } } else @@ -338,7 +340,7 @@ private class Parser(fromPos: Pos) { // could be integer, also hex: if (currentChar == 'x' && p1 == "0") { pos.advance() - Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also { + Token(loadChars { it in hexDigits }, start, Token.Type.HEX).also { if (currentChar.isLetter()) raise("invalid hex literal") } diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Token.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Token.kt index e6e8831..df82c10 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Token.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Token.kt @@ -17,7 +17,6 @@ data class Token(val value: String, val pos: Pos, val type: Type) { AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON, SINLGE_LINE_COMMENT, MULTILINE_COMMENT, LABEL, ATLABEL, // label@ at@label - PRIVATE, PROTECTED, //PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS, ELLIPSIS, DOTDOT, DOTDOTLT, NEWLINE, diff --git a/library/src/commonTest/kotlin/ScriptTest.kt b/library/src/commonTest/kotlin/ScriptTest.kt index 5d4136d..c12fa19 100644 --- a/library/src/commonTest/kotlin/ScriptTest.kt +++ b/library/src/commonTest/kotlin/ScriptTest.kt @@ -1876,4 +1876,23 @@ class ScriptTest { """.trimIndent()) } + @Test + fun testReturnValue1() = runTest { + val r = eval(""" + class Point(x,y) { + println("1") + fun length() { sqrt(d2()) } + println("2") + private fun d2() {x*x + y*y} + println("3") + } + println("Helluva") + val p = Point(3,4) +// assertEquals( 5, p.length() ) +// assertThrows { p.d2() } + "111" + """.trimIndent()) + assertEquals("111", r.toString()) + } + } \ No newline at end of file