ref #13 get rid pf tokens for private/protected, added open attribute parsing

This commit is contained in:
Sergey Chernov 2025-06-13 10:31:32 +04:00
parent 59a76efdce
commit dacdcd7faa
5 changed files with 121 additions and 85 deletions

View File

@ -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 -> { Token.Type.PLUS2, Token.Type.MINUS2 -> {
cc.previous() cc.previous()
parseExpression(cc) parseExpression(cc)
@ -149,7 +142,7 @@ class Compiler(
v.invokeInstanceMethod( v.invokeInstanceMethod(
context, context,
next.value, next.value,
args.toArguments(context,false) args.toArguments(context, false)
), isMutable = false ), isMutable = false
) )
} }
@ -170,7 +163,7 @@ class Compiler(
v.invokeInstanceMethod( v.invokeInstanceMethod(
context, context,
next.value, next.value,
Arguments(listOf(lambda),true) Arguments(listOf(lambda), true)
), isMutable = false ), isMutable = false
) )
} }
@ -458,17 +451,14 @@ class Compiler(
} }
Token.Type.NEWLINE -> {} Token.Type.NEWLINE -> {}
Token.Type.PROTECTED, Token.Type.PRIVATE -> {
if (!isClassDeclaration) {
cc.restorePos(startPos); return null
}
}
Token.Type.ID -> { Token.Type.ID -> {
// visibility // visibility
val visibility = if (isClassDeclaration) val visibility = if( isClassDeclaration && t.value == "private" ) {
cc.getVisibility(Visibility.Public) t = cc.next()
else Visibility.Public Visibility.Private
} else Visibility.Public
// val/var? // val/var?
val access = when (t.value) { val access = when (t.value) {
"val" -> { "val" -> {
@ -707,8 +697,8 @@ class Compiler(
* @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) {
"val" -> parseVarDeclaration(id.value, false, cc) "val" -> parseVarDeclaration(false, Visibility.Public, cc)
"var" -> parseVarDeclaration(id.value, true, cc) "var" -> parseVarDeclaration(true, Visibility.Public, cc)
"while" -> parseWhileStatement(cc) "while" -> parseWhileStatement(cc)
"do" -> parseDoWhileStatement(cc) "do" -> parseDoWhileStatement(cc)
"for" -> parseForStatement(cc) "for" -> parseForStatement(cc)
@ -719,16 +709,33 @@ class Compiler(
"class" -> parseClassDeclaration(cc, false) "class" -> parseClassDeclaration(cc, false)
"try" -> parseTryStatement(cc) "try" -> parseTryStatement(cc)
"throw" -> parseThrowStatement(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 { private fun parseThrowStatement(cc: CompilerContext): Statement {
val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected") val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected")
return statement { return statement {
var errorObject = throwStatement.execute(this) var errorObject = throwStatement.execute(this)
if( errorObject is ObjString ) if (errorObject is ObjString)
errorObject = ObjException(this, errorObject.value) errorObject = ObjException(this, errorObject.value)
if( errorObject is ObjException ) if (errorObject is ObjException)
raiseError(errorObject) raiseError(errorObject)
else raiseError("this is not an exception object: $errorObject") else raiseError("this is not an exception object: $errorObject")
} }
@ -745,7 +752,7 @@ class Compiler(
val catches = mutableListOf<CatchBlockData>() val catches = mutableListOf<CatchBlockData>()
cc.skipTokens(Token.Type.NEWLINE) cc.skipTokens(Token.Type.NEWLINE)
var t = cc.next() var t = cc.next()
while( t.value == "catch" ) { while (t.value == "catch") {
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
t = cc.next() t = cc.next()
@ -785,19 +792,21 @@ class Compiler(
} else { } else {
// no (e: Exception) block: should be shortest variant `catch { ... }` // no (e: Exception) block: should be shortest variant `catch { ... }`
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here") cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
catches += CatchBlockData(Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"), catches += CatchBlockData(
parseBlock(cc,true)) Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
parseBlock(cc, true)
)
t = cc.next() t = cc.next()
} }
} }
val finallyClause = if( t.value == "finally" ) { val finallyClause = if (t.value == "finally") {
parseBlock(cc) parseBlock(cc)
} else { } else {
cc.previous() cc.previous()
null 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") throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
return statement { return statement {
@ -805,26 +814,25 @@ class Compiler(
try { try {
// body is a parsed block, it already has separate context // body is a parsed block, it already has separate context
result = body.execute(this) result = body.execute(this)
} } catch (e: Exception) {
catch (e: Exception) {
// convert to appropriate exception // convert to appropriate exception
val objException = when(e) { val objException = when (e) {
is ExecutionError -> e.errorObject is ExecutionError -> e.errorObject
else -> ObjUnknownException(this, e.message ?: e.toString()) else -> ObjUnknownException(this, e.message ?: e.toString())
} }
// let's see if we should catch it: // let's see if we should catch it:
var isCaught = false var isCaught = false
for( cdata in catches ) { for (cdata in catches) {
var exceptionObject: ObjException? = null var exceptionObject: ObjException? = null
for (exceptionClassName in cdata.classNames) { for (exceptionClassName in cdata.classNames) {
val exObj = ObjException.getErrorClass(exceptionClassName) val exObj = ObjException.getErrorClass(exceptionClassName)
?: raiseSymbolNotFound("error clas not exists: $exceptionClassName") ?: raiseSymbolNotFound("error clas not exists: $exceptionClassName")
if( objException.isInstanceOf(exObj) ) { if (objException.isInstanceOf(exObj)) {
exceptionObject = objException exceptionObject = objException
break break
} }
} }
if( exceptionObject != null ) { if (exceptionObject != null) {
val catchContext = this.copy(pos = cdata.catchVar.pos) val catchContext = this.copy(pos = cdata.catchVar.pos)
catchContext.addItem(cdata.catchVar.value, false, objException) catchContext.addItem(cdata.catchVar.value, false, objException)
result = cdata.block.execute(catchContext) result = cdata.block.execute(catchContext)
@ -833,10 +841,9 @@ class Compiler(
} }
} }
// rethrow if not caught this exception // rethrow if not caught this exception
if( !isCaught ) if (!isCaught)
throw e throw e
} } finally {
finally {
// finally clause does not alter result! // finally clause does not alter result!
finallyClause?.execute(this) finallyClause?.execute(this)
} }
@ -1081,7 +1088,7 @@ class Compiler(
cc.skipTokens(Token.Type.NEWLINE) cc.skipTokens(Token.Type.NEWLINE)
val t = cc.next() val t = cc.next()
if( t.type != Token.Type.ID && t.value != "while" ) if (t.type != Token.Type.ID && t.value != "while")
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here") cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
val conditionStart = ensureLparen(cc) val conditionStart = ensureLparen(cc)
@ -1106,10 +1113,9 @@ class Compiler(
doContext = it.copy().apply { skipContextCreation = true } doContext = it.copy().apply { skipContextCreation = true }
try { try {
result = body.execute(doContext) result = body.execute(doContext)
} } catch (e: LoopBreakContinueException) {
catch( e: LoopBreakContinueException) { if (e.label == label || e.label == null) {
if( e.label == label || e.label == null ) { if (e.doContinue) continue
if( e.doContinue ) continue
else { else {
result = e.result result = e.result
wasBroken = true wasBroken = true
@ -1118,8 +1124,8 @@ class Compiler(
} }
throw e throw e
} }
} while( condition.execute(doContext).toBool() ) } while (condition.execute(doContext).toBool())
if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) } if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
result result
} }
} }
@ -1273,9 +1279,11 @@ class Compiler(
} }
} }
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement { private fun parseFunctionDeclaration(
val visibility = tokens.getVisibility() tokens: CompilerContext,
visibility: Visibility = Visibility.Public,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
): Statement {
var t = tokens.next() var t = tokens.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)
@ -1329,7 +1337,7 @@ class Compiler(
val block = parseScript(startPos, cc) val block = parseScript(startPos, cc)
return statement(startPos) { return statement(startPos) {
// block run on inner context: // 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 { }.also {
val t1 = cc.next() val t1 = cc.next()
if (t1.type != Token.Type.RBRACE) if (t1.type != Token.Type.RBRACE)
@ -1337,26 +1345,22 @@ class Compiler(
} }
} }
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement { private fun parseVarDeclaration(
// we are just after var/val, visibility if exists is 2 steps behind isMutable: Boolean,
val visibility = when (tokens.atOffset(-2)?.type) { visibility: Visibility,
Token.Type.PRIVATE -> tokens: CompilerContext,
Visibility.Private @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
): Statement {
Token.Type.PROTECTED -> Visibility.Protected
else -> Visibility.Public
}
val nameToken = tokens.next() val nameToken = tokens.next()
val start = nameToken.pos val start = nameToken.pos
if (nameToken.type != Token.Type.ID) 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 name = nameToken.value
val eqToken = tokens.next() val eqToken = tokens.next()
var setNull = false var setNull = false
if (eqToken.type != Token.Type.ASSIGN) { if (eqToken.type != Token.Type.ASSIGN) {
if (!mutable) if (!isMutable)
throw ScriptError(start, "val must be initialized") throw ScriptError(start, "val must be initialized")
else { else {
tokens.previous() tokens.previous()
@ -1375,7 +1379,7 @@ class Compiler(
// create a separate copy: // create a separate copy:
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(name, mutable, initValue, visibility) context.addItem(name, isMutable, initValue, visibility)
initValue initValue
} }
} }
@ -1519,7 +1523,8 @@ class Compiler(
/** /**
* The keywords that stop processing of expression term * 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")
} }
} }

View File

@ -102,6 +102,7 @@ internal class CompilerContext(val tokens: List<Token>) {
* Return value of the next token if it is an identifier, null otherwise. * Return value of the next token if it is an identifier, null otherwise.
* Does not change position. * Does not change position.
*/ */
@Suppress("unused")
fun nextIdValue(): String? { fun nextIdValue(): String? {
return if (hasNext()) { return if (hasNext()) {
val nt = tokens[currentIndex] val nt = tokens[currentIndex]
@ -117,21 +118,31 @@ internal class CompilerContext(val tokens: List<Token>) {
/** /**
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null. * If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
*/ */
@Suppress("unused")
fun atOffset(offset: Int): Token? = fun atOffset(offset: Int): Token? =
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
/** fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
* Scan backwards as deep as specified looking for visibility token. Does not change position. val pos = savePos()
*/ var count = 0
fun getVisibility(default: Visibility = Visibility.Public, depths: Int = 2): Visibility { while( count < qualifiers.size) {
for( i in -depths .. -1) { val t = next()
when( atOffset(i)?.type) { when(t.type) {
Token.Type.PROTECTED -> return Visibility.Protected Token.Type.ID -> {
Token.Type.PRIVATE -> return Visibility.Private if( t.value in qualifiers ) count++
else -> {} 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 { // fun expectKeyword(vararg keyword: String): String {

View File

@ -137,15 +137,19 @@ private class Parser(fromPos: Pos) {
if (currentChar == '.') { if (currentChar == '.') {
pos.advance() pos.advance()
// .. already parsed: // .. already parsed:
if (currentChar == '.') { when (currentChar) {
'.' -> {
pos.advance() pos.advance()
Token("...", from, Token.Type.ELLIPSIS) Token("...", from, Token.Type.ELLIPSIS)
} else if (currentChar == '<') { }
'<' -> {
pos.advance() pos.advance()
Token("..<", from, Token.Type.DOTDOTLT) Token("..<", from, Token.Type.DOTDOTLT)
} else { }
else -> {
Token("..", from, Token.Type.DOTDOT) Token("..", from, Token.Type.DOTDOT)
} }
}
} else } else
Token(".", from, Token.Type.DOT) Token(".", from, Token.Type.DOT)
} }
@ -280,8 +284,6 @@ private class Parser(fromPos: Pos) {
when (text) { when (text) {
"in" -> Token("in", from, Token.Type.IN) "in" -> Token("in", from, Token.Type.IN)
"is" -> Token("is", from, Token.Type.IS) "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 -> Token(text, from, Token.Type.ID)
} }
} else } else
@ -338,7 +340,7 @@ private class Parser(fromPos: Pos) {
// could be integer, also hex: // could be integer, also hex:
if (currentChar == 'x' && p1 == "0") { if (currentChar == 'x' && p1 == "0") {
pos.advance() 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()) if (currentChar.isLetter())
raise("invalid hex literal") raise("invalid hex literal")
} }

View File

@ -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, AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
SINLGE_LINE_COMMENT, MULTILINE_COMMENT, SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
LABEL, ATLABEL, // label@ at@label 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, //PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
ELLIPSIS, DOTDOT, DOTDOTLT, ELLIPSIS, DOTDOT, DOTDOTLT,
NEWLINE, NEWLINE,

View File

@ -1876,4 +1876,23 @@ class ScriptTest {
""".trimIndent()) """.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())
}
} }