fix #36 basic import support
This commit is contained in:
parent
ddbcbf9e4e
commit
f37354f382
@ -5,24 +5,69 @@ package net.sergeych.lyng
|
|||||||
*/
|
*/
|
||||||
class Compiler(
|
class Compiler(
|
||||||
val cc: CompilerContext,
|
val cc: CompilerContext,
|
||||||
|
val pacman: Pacman = Pacman.emptyAllowAll,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
settings: Settings = Settings()
|
settings: Settings = Settings()
|
||||||
) {
|
) {
|
||||||
|
var packageName: String? = null
|
||||||
|
|
||||||
class Settings
|
class Settings
|
||||||
|
|
||||||
private fun parseScript(): Script {
|
private suspend fun parseScript(): Script {
|
||||||
val statements = mutableListOf<Statement>()
|
val statements = mutableListOf<Statement>()
|
||||||
val start = cc.currentPos()
|
val start = cc.currentPos()
|
||||||
// val returnScope = cc.startReturnScope()
|
// val returnScope = cc.startReturnScope()
|
||||||
while (parseStatement( braceMeansLambda = true)?.also {
|
// package level declarations
|
||||||
|
do {
|
||||||
|
val t = cc.current()
|
||||||
|
if (t.type == Token.Type.ID) {
|
||||||
|
when (t.value) {
|
||||||
|
"package" -> {
|
||||||
|
cc.next()
|
||||||
|
val name = loadQualifiedName()
|
||||||
|
if (name.isEmpty()) throw ScriptError(cc.currentPos(), "Expecting package name here")
|
||||||
|
if (packageName != null) throw ScriptError(
|
||||||
|
cc.currentPos(),
|
||||||
|
"package name redefined, already set to $packageName"
|
||||||
|
)
|
||||||
|
packageName = name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
"import" -> {
|
||||||
|
cc.next()
|
||||||
|
val pos = cc.currentPos()
|
||||||
|
val name = loadQualifiedName()
|
||||||
|
pacman.prepareImport(pos, name, null)
|
||||||
|
statements += statement {
|
||||||
|
pacman.performImport(this,name,null)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parseStatement(braceMeansLambda = true)?.also {
|
||||||
statements += it
|
statements += it
|
||||||
} != null) {/**/
|
} ?: break
|
||||||
}
|
|
||||||
|
} while (true)
|
||||||
return Script(start, statements)//returnScope.needCatch)
|
return Script(start, statements)//returnScope.needCatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
fun loadQualifiedName(): String {
|
||||||
|
val result = StringBuilder()
|
||||||
|
var t = cc.next()
|
||||||
|
while (t.type == Token.Type.ID) {
|
||||||
|
result.append(t.value)
|
||||||
|
t = cc.next()
|
||||||
|
if (t.type == Token.Type.DOT) {
|
||||||
|
result.append('.')
|
||||||
|
t = cc.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
||||||
while (true) {
|
while (true) {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
@ -70,15 +115,15 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseExpression(): Statement? {
|
private suspend fun parseExpression(): Statement? {
|
||||||
val pos = cc.currentPos()
|
val pos = cc.currentPos()
|
||||||
return parseExpressionLevel()?.let { a -> statement(pos) { a.getter(it).value } }
|
return parseExpressionLevel()?.let { a -> statement(pos) { a.getter(it).value } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseExpressionLevel(level: Int = 0): Accessor? {
|
private suspend fun parseExpressionLevel(level: Int = 0): Accessor? {
|
||||||
if (level == lastLevel)
|
if (level == lastLevel)
|
||||||
return parseTerm()
|
return parseTerm()
|
||||||
var lvalue: Accessor? = parseExpressionLevel( level + 1) ?: return null
|
var lvalue: Accessor? = parseExpressionLevel(level + 1) ?: return null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
@ -89,7 +134,7 @@ class Compiler(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val rvalue = parseExpressionLevel( level + 1)
|
val rvalue = parseExpressionLevel(level + 1)
|
||||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
?: throw ScriptError(opToken.pos, "Expecting expression")
|
||||||
|
|
||||||
lvalue = op.generate(opToken.pos, lvalue!!, rvalue)
|
lvalue = op.generate(opToken.pos, lvalue!!, rvalue)
|
||||||
@ -97,7 +142,7 @@ class Compiler(
|
|||||||
return lvalue
|
return lvalue
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseTerm(): Accessor? {
|
private suspend fun parseTerm(): Accessor? {
|
||||||
var operand: Accessor? = null
|
var operand: Accessor? = null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -178,7 +223,7 @@ class Compiler(
|
|||||||
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
||||||
else x.readField(context, next.value)
|
else x.readField(context, next.value)
|
||||||
}) { cxt, newValue ->
|
}) { cxt, newValue ->
|
||||||
left.getter(cxt).value.writeField(cxt, next.value, newValue)
|
left.getter(cxt).value.writeField(cxt, next.value, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,12 +416,15 @@ class Compiler(
|
|||||||
/**
|
/**
|
||||||
* Parse lambda expression, leading '{' is already consumed
|
* Parse lambda expression, leading '{' is already consumed
|
||||||
*/
|
*/
|
||||||
private fun parseLambdaExpression(): Accessor {
|
private suspend fun parseLambdaExpression(): Accessor {
|
||||||
// lambda args are different:
|
// lambda args are different:
|
||||||
val startPos = cc.currentPos()
|
val startPos = cc.currentPos()
|
||||||
val argsDeclaration = parseArgsDeclaration()
|
val argsDeclaration = parseArgsDeclaration()
|
||||||
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 body = parseBlock(skipLeadingBrace = true)
|
val body = parseBlock(skipLeadingBrace = true)
|
||||||
|
|
||||||
@ -410,7 +458,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseArrayLiteral(): List<ListEntry> {
|
private suspend fun parseArrayLiteral(): List<ListEntry> {
|
||||||
// it should be called after Token.Type.LBRACKET is consumed
|
// it should be called after Token.Type.LBRACKET is consumed
|
||||||
val entries = mutableListOf<ListEntry>()
|
val entries = mutableListOf<ListEntry>()
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -452,7 +500,7 @@ class Compiler(
|
|||||||
* Parse argument declaration, used in lambda (and later in fn too)
|
* Parse argument declaration, used in lambda (and later in fn too)
|
||||||
* @return declaration or null if there is no valid list of arguments
|
* @return declaration or null if there is no valid list of arguments
|
||||||
*/
|
*/
|
||||||
private fun parseArgsDeclaration(isClassDeclaration: Boolean = false): ArgsDeclaration? {
|
private suspend fun parseArgsDeclaration(isClassDeclaration: Boolean = false): ArgsDeclaration? {
|
||||||
val result = mutableListOf<ArgsDeclaration.Item>()
|
val result = mutableListOf<ArgsDeclaration.Item>()
|
||||||
var endTokenType: Token.Type? = null
|
var endTokenType: Token.Type? = null
|
||||||
val startPos = cc.savePos()
|
val startPos = cc.savePos()
|
||||||
@ -559,7 +607,7 @@ class Compiler(
|
|||||||
* Parse arguments list during the call and detect last block argument
|
* Parse arguments list during the call and detect last block argument
|
||||||
* _following the parenthesis_ call: `(1,2) { ... }`
|
* _following the parenthesis_ call: `(1,2) { ... }`
|
||||||
*/
|
*/
|
||||||
private fun parseArgs(): Pair<List<ParsedArgument>, Boolean> {
|
private suspend fun parseArgs(): Pair<List<ParsedArgument>, Boolean> {
|
||||||
|
|
||||||
val args = mutableListOf<ParsedArgument>()
|
val args = mutableListOf<ParsedArgument>()
|
||||||
do {
|
do {
|
||||||
@ -605,7 +653,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun parseFunctionCall(
|
private suspend fun parseFunctionCall(
|
||||||
left: Accessor,
|
left: Accessor,
|
||||||
blockArgument: Boolean,
|
blockArgument: Boolean,
|
||||||
isOptional: Boolean
|
isOptional: Boolean
|
||||||
@ -716,7 +764,7 @@ class Compiler(
|
|||||||
* Parse keyword-starting statement.
|
* 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): Statement? = when (id.value) {
|
private suspend fun parseKeywordStatement(id: Token): Statement? = when (id.value) {
|
||||||
"val" -> parseVarDeclaration(false, Visibility.Public)
|
"val" -> parseVarDeclaration(false, Visibility.Public)
|
||||||
"var" -> parseVarDeclaration(true, Visibility.Public)
|
"var" -> parseVarDeclaration(true, Visibility.Public)
|
||||||
"while" -> parseWhileStatement()
|
"while" -> parseWhileStatement()
|
||||||
@ -734,13 +782,13 @@ class Compiler(
|
|||||||
cc.previous()
|
cc.previous()
|
||||||
val isExtern = cc.skipId("extern")
|
val isExtern = cc.skipId("extern")
|
||||||
when {
|
when {
|
||||||
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration( Visibility.Private, isExtern)
|
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
||||||
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration( Visibility.Private, isExtern)
|
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
||||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration( isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration( isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
|
|
||||||
cc.matchQualifiers("fun") -> parseFunctionDeclaration( isOpen = false, isExtern = isExtern)
|
cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn") -> parseFunctionDeclaration( isOpen = false, isExtern = isExtern)
|
cc.matchQualifiers("fn") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
||||||
|
|
||||||
cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private)
|
cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private)
|
||||||
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private)
|
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private)
|
||||||
@ -756,7 +804,7 @@ class Compiler(
|
|||||||
|
|
||||||
data class WhenCase(val condition: Statement, val block: Statement)
|
data class WhenCase(val condition: Statement, val block: Statement)
|
||||||
|
|
||||||
private fun parseWhenStatement(): Statement {
|
private suspend fun parseWhenStatement(): Statement {
|
||||||
// has a value, when(value) ?
|
// has a value, when(value) ?
|
||||||
var t = cc.skipWsTokens()
|
var t = cc.skipWsTokens()
|
||||||
return if (t.type == Token.Type.LPAREN) {
|
return if (t.type == Token.Type.LPAREN) {
|
||||||
@ -822,7 +870,10 @@ class Compiler(
|
|||||||
"when else block already defined"
|
"when else block already defined"
|
||||||
)
|
)
|
||||||
elseCase =
|
elseCase =
|
||||||
parseStatement() ?: throw ScriptError(cc.currentPos(), "when else block expected")
|
parseStatement() ?: throw ScriptError(
|
||||||
|
cc.currentPos(),
|
||||||
|
"when else block expected"
|
||||||
|
)
|
||||||
skipParseBody = true
|
skipParseBody = true
|
||||||
} else {
|
} else {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
@ -861,7 +912,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseThrowStatement(): Statement {
|
private suspend fun parseThrowStatement(): Statement {
|
||||||
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
|
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
|
||||||
return statement {
|
return statement {
|
||||||
var errorObject = throwStatement.execute(this)
|
var errorObject = throwStatement.execute(this)
|
||||||
@ -879,7 +930,7 @@ class Compiler(
|
|||||||
val block: Statement
|
val block: Statement
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun parseTryStatement(): Statement {
|
private suspend fun parseTryStatement(): Statement {
|
||||||
val body = parseBlock()
|
val body = parseBlock()
|
||||||
val catches = mutableListOf<CatchBlockData>()
|
val catches = mutableListOf<CatchBlockData>()
|
||||||
cc.skipTokens(Token.Type.NEWLINE)
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
@ -983,11 +1034,11 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseClassDeclaration(isStruct: Boolean): Statement {
|
private suspend fun parseClassDeclaration(isStruct: Boolean): Statement {
|
||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
val nameToken = cc.requireToken(Token.Type.ID)
|
||||||
val constructorArgsDeclaration =
|
val constructorArgsDeclaration =
|
||||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||||
parseArgsDeclaration( isClassDeclaration = true)
|
parseArgsDeclaration(isClassDeclaration = true)
|
||||||
else null
|
else null
|
||||||
|
|
||||||
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
@ -1059,7 +1110,7 @@ class Compiler(
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseForStatement(): Statement {
|
private suspend fun parseForStatement(): Statement {
|
||||||
val label = getLabel()?.also { cc.labels += it }
|
val label = getLabel()?.also { cc.labels += it }
|
||||||
val start = ensureLparen()
|
val start = ensureLparen()
|
||||||
|
|
||||||
@ -1210,7 +1261,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
private fun parseDoWhileStatement(): Statement {
|
private suspend fun parseDoWhileStatement(): Statement {
|
||||||
val label = getLabel()?.also { cc.labels += it }
|
val label = getLabel()?.also { cc.labels += it }
|
||||||
val (breakFound, body) = cc.parseLoop {
|
val (breakFound, body) = cc.parseLoop {
|
||||||
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad while statement: expected statement")
|
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad while statement: expected statement")
|
||||||
@ -1262,7 +1313,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseWhileStatement(): Statement {
|
private suspend fun parseWhileStatement(): Statement {
|
||||||
val label = getLabel()?.also { cc.labels += it }
|
val label = getLabel()?.also { cc.labels += it }
|
||||||
val start = ensureLparen()
|
val start = ensureLparen()
|
||||||
val condition =
|
val condition =
|
||||||
@ -1304,7 +1355,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseBreakStatement(start: Pos): Statement {
|
private suspend fun parseBreakStatement(start: Pos): Statement {
|
||||||
var t = cc.next()
|
var t = cc.next()
|
||||||
|
|
||||||
val label = if (t.pos.line != start.line || t.type != Token.Type.ATLABEL) {
|
val label = if (t.pos.line != start.line || t.type != Token.Type.ATLABEL) {
|
||||||
@ -1376,7 +1427,7 @@ class Compiler(
|
|||||||
return t.pos
|
return t.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseIfStatement(): Statement {
|
private suspend fun parseIfStatement(): Statement {
|
||||||
val start = ensureLparen()
|
val start = ensureLparen()
|
||||||
|
|
||||||
val condition = parseExpression()
|
val condition = parseExpression()
|
||||||
@ -1411,7 +1462,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFunctionDeclaration(
|
private suspend fun parseFunctionDeclaration(
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||||
isExtern: Boolean = false
|
isExtern: Boolean = false
|
||||||
@ -1425,11 +1476,11 @@ class Compiler(
|
|||||||
|
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
// Is extension?
|
// Is extension?
|
||||||
if( t.type == Token.Type.DOT) {
|
if (t.type == Token.Type.DOT) {
|
||||||
extTypeName = name
|
extTypeName = name
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
if( t.type != Token.Type.ID)
|
if (t.type != Token.Type.ID)
|
||||||
throw ScriptError(t.pos, "illegal extension format: expected function name")
|
throw ScriptError(t.pos, "illegal extension format: expected function name")
|
||||||
name = t.value
|
name = t.value
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
}
|
}
|
||||||
@ -1462,7 +1513,7 @@ class Compiler(
|
|||||||
|
|
||||||
// load params from caller context
|
// load params from caller context
|
||||||
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
||||||
if( extTypeName != null ) {
|
if (extTypeName != null) {
|
||||||
context.thisObj = callerContext.thisObj
|
context.thisObj = callerContext.thisObj
|
||||||
}
|
}
|
||||||
fnStatements.execute(context)
|
fnStatements.execute(context)
|
||||||
@ -1474,12 +1525,12 @@ class Compiler(
|
|||||||
extTypeName?.let { typeName ->
|
extTypeName?.let { typeName ->
|
||||||
// class extension method
|
// class extension method
|
||||||
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
||||||
if( type !is ObjClass ) context.raiseClassCastError("$typeName is not the class instance")
|
if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance")
|
||||||
type.addFn( name, isOpen = true) {
|
type.addFn(name, isOpen = true) {
|
||||||
fnBody.execute(this)
|
fnBody.execute(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// regular function/method
|
// regular function/method
|
||||||
?: context.addItem(name, false, fnBody, visibility)
|
?: context.addItem(name, false, fnBody, visibility)
|
||||||
// as the function can be called from anywhere, we have
|
// as the function can be called from anywhere, we have
|
||||||
// saved the proper context in the closure
|
// saved the proper context in the closure
|
||||||
@ -1487,7 +1538,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
||||||
val startPos = cc.currentPos()
|
val startPos = cc.currentPos()
|
||||||
if (!skipLeadingBrace) {
|
if (!skipLeadingBrace) {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
@ -1505,7 +1556,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseVarDeclaration(
|
private suspend fun parseVarDeclaration(
|
||||||
isMutable: Boolean,
|
isMutable: Boolean,
|
||||||
visibility: Visibility,
|
visibility: Visibility,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
||||||
@ -1527,7 +1578,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val initialExpression = if (setNull) null else parseStatement( true)
|
val initialExpression = if (setNull) null else parseStatement(true)
|
||||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
|
|
||||||
return statement(nameToken.pos) { context ->
|
return statement(nameToken.pos) { context ->
|
||||||
@ -1561,8 +1612,15 @@ class Compiler(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun compile(source: Source): Script {
|
suspend fun compile(source: Source,pacman: Pacman = Pacman.emptyAllowAll): Script {
|
||||||
return Compiler(CompilerContext(parseLyng(source))).parseScript()
|
return Compiler(CompilerContext(parseLyng(source)),pacman).parseScript()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun compilePackage(source: Source): Pair<String, Script> {
|
||||||
|
val c = Compiler(CompilerContext(parseLyng(source)))
|
||||||
|
val script = c.parseScript()
|
||||||
|
if (c.packageName == null) throw ScriptError(source.startPos, "package not set")
|
||||||
|
return c.packageName!! to script
|
||||||
}
|
}
|
||||||
|
|
||||||
private var lastPriority = 0
|
private var lastPriority = 0
|
||||||
@ -1685,7 +1743,7 @@ class Compiler(
|
|||||||
allOps.filter { it.priority == l }.associateBy { it.tokenType }
|
allOps.filter { it.priority == l }.associateBy { it.tokenType }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compile(code: String): Script = compile(Source("<eval>", code))
|
suspend fun compile(code: String): Script = compile(Source("<eval>", code))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The keywords that stop processing of expression term
|
* The keywords that stop processing of expression term
|
||||||
|
@ -97,7 +97,7 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
inline fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
||||||
val t = next()
|
val t = next()
|
||||||
return if (t.type == typeId) {
|
return if (t.type == typeId) {
|
||||||
f(t)
|
f(t)
|
||||||
|
130
lynglib/src/commonMain/kotlin/net/sergeych/lyng/Importer.kt
Normal file
130
lynglib/src/commonMain/kotlin/net/sergeych/lyng/Importer.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import net.sergeych.mp_tools.globalDefer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package manager. Chained manager, too simple. Override [createModuleScope] to return either
|
||||||
|
* valid [ModuleScope] or call [parent] - or return null.
|
||||||
|
*/
|
||||||
|
abstract class Pacman(
|
||||||
|
val parent: Pacman? = null,
|
||||||
|
val rootScope: Scope = parent!!.rootScope,
|
||||||
|
val securityManager: SecurityManager = parent!!.securityManager
|
||||||
|
) {
|
||||||
|
private val opScopes = Mutex()
|
||||||
|
|
||||||
|
private val cachedScopes = mutableMapOf<String, Scope>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new module scope if this pacman can import the module, return null otherwise so
|
||||||
|
* the manager can decide what to do
|
||||||
|
*/
|
||||||
|
abstract suspend fun createModuleScope(name: String): ModuleScope?
|
||||||
|
|
||||||
|
suspend fun prepareImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
if (!securityManager.canImportModule(name))
|
||||||
|
throw ImportException(pos, "Module $name is not allowed")
|
||||||
|
symbols?.keys?.forEach {
|
||||||
|
if (!securityManager.canImportSymbol(name, it)) throw ImportException(
|
||||||
|
pos,
|
||||||
|
"Symbol $name.$it is not allowed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if we can import the module, cache it, or go further
|
||||||
|
opScopes.withLock {
|
||||||
|
cachedScopes[name] ?: createModuleScope(name)?.let { cachedScopes[name] = it }
|
||||||
|
} ?: parent?.prepareImport(pos, name, symbols) ?: throw ImportException(pos, "Module $name is not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun performImport(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
val module = opScopes.withLock { cachedScopes[name] }
|
||||||
|
?: scope.raiseSymbolNotFound("module $name not found")
|
||||||
|
val symbolsToImport = symbols?.keys?.toMutableSet()
|
||||||
|
for ((symbol, record) in module.objects) {
|
||||||
|
if (record.visibility.isPublic) {
|
||||||
|
println("import $name: $symbol: $record")
|
||||||
|
val newName = symbols?.let { ss: Map<String, String> ->
|
||||||
|
ss[symbol]
|
||||||
|
?.also { symbolsToImport!!.remove(it) }
|
||||||
|
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
|
||||||
|
} ?: symbol
|
||||||
|
println("import $name.$symbol as $newName")
|
||||||
|
if (newName in scope.objects)
|
||||||
|
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
|
||||||
|
scope.objects[newName] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!symbolsToImport.isNullOrEmpty())
|
||||||
|
scope.raiseSymbolNotFound("symbols $name.{$symbolsToImport} are.were not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val emptyAllowAll = object : Pacman(rootScope = Script.defaultScope, securityManager = SecurityManager.allowAll) {
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope supports importing and contains the [pacman]; it should be the same
|
||||||
|
* used in [Compiler];
|
||||||
|
*/
|
||||||
|
class ModuleScope(
|
||||||
|
val pacman: Pacman,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
|
val packageName: String
|
||||||
|
) : Scope(pacman.rootScope, Arguments.EMPTY, pos) {
|
||||||
|
|
||||||
|
constructor(pacman: Pacman,source: Source) : this(pacman, source.startPos, source.fileName)
|
||||||
|
|
||||||
|
override suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.prepareImport(pos, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import symbols into the scope. It _is called_ after the module is imported by [checkImport].
|
||||||
|
* If [checkImport] was not called, the symbols will not be imported with exception as module is not found.
|
||||||
|
*/
|
||||||
|
override suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.performImport(scope, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageNameObj by lazy { ObjString(packageName).asReadonly}
|
||||||
|
|
||||||
|
override fun get(name: String): ObjRecord? {
|
||||||
|
return if( name == "__PACKAGE__")
|
||||||
|
packageNameObj
|
||||||
|
else
|
||||||
|
super.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InlineSourcesPacman(pacman: Pacman,val sources: List<Source>) : Pacman(pacman) {
|
||||||
|
|
||||||
|
val modules: Deferred<Map<String,Deferred<ModuleScope>>> = globalDefer {
|
||||||
|
val result = mutableMapOf<String, Deferred<ModuleScope>>()
|
||||||
|
for (source in sources) {
|
||||||
|
// retrieve the module name and script for deferred execution:
|
||||||
|
val (name, script) = Compiler.compilePackage(source)
|
||||||
|
// scope is created used pacman's root scope:
|
||||||
|
val scope = ModuleScope(this@InlineSourcesPacman, source.startPos, name)
|
||||||
|
// we execute scripts in parallel which allow cross-imports to some extent:
|
||||||
|
result[name] = globalDefer { script.execute(scope); scope }
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? =
|
||||||
|
modules.await()[name]?.await()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -510,3 +510,6 @@ class ObjAccessException(scope: Scope, message: String = "access not allowed err
|
|||||||
|
|
||||||
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
|
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
|
||||||
ObjException("UnknownException", scope, message)
|
ObjException("UnknownException", scope, message)
|
||||||
|
|
||||||
|
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
|
||||||
|
ObjException("IllegalOperationException", scope, message)
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope is where local variables and methods are stored. Scope is also a parent scope for other scopes.
|
||||||
|
* Each block usually creates a scope. Accessing Lyng closures usually is done via a scope.
|
||||||
|
*
|
||||||
|
* There are special types of scopes:
|
||||||
|
*
|
||||||
|
* - [Script.defaultScope] - root scope for a script, safe one
|
||||||
|
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
|
||||||
|
*/
|
||||||
open class Scope(
|
open class Scope(
|
||||||
val parent: Scope?,
|
val parent: Scope?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
@ -125,6 +134,19 @@ open class Scope(
|
|||||||
suspend fun eval(code: String): Obj =
|
suspend fun eval(code: String): Obj =
|
||||||
Compiler.compile(code.toSource()).execute(this)
|
Compiler.compile(code.toSource()).execute(this)
|
||||||
|
|
||||||
|
suspend fun eval(source: Source): Obj =
|
||||||
|
Compiler.compile(
|
||||||
|
source,
|
||||||
|
(this as? ModuleScope)?.pacman?.also { println("pacman found: $pacman")} ?: Pacman.emptyAllowAll
|
||||||
|
).execute(this)
|
||||||
|
|
||||||
fun containsLocal(name: String): Boolean = name in objects
|
fun containsLocal(name: String): Boolean = name in objects
|
||||||
|
|
||||||
|
open suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
throw ImportException(pos, "Import is not allowed here: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
scope.raiseError(ObjIllegalOperationException(scope,"Import is not allowed here: import $name"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
interface SecurityManager {
|
||||||
|
/**
|
||||||
|
* Check that any symbol from the corresponding module can be imported. If it returns false,
|
||||||
|
* the module will not be imported and no further processing will be done
|
||||||
|
*/
|
||||||
|
fun canImportModule(name: String): Boolean
|
||||||
|
|
||||||
|
/**\
|
||||||
|
* if [canImportModule] this method allows fine-grained control over symbols.
|
||||||
|
*/
|
||||||
|
fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val allowAll: SecurityManager = object : SecurityManager {
|
||||||
|
override fun canImportModule(name: String): Boolean = true
|
||||||
|
override fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2332,4 +2332,37 @@ class ScriptTest {
|
|||||||
""")
|
""")
|
||||||
listOf(1,2,3).associateBy { it * 10 }
|
listOf(1,2,3).associateBy { it * 10 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImports1() = runTest() {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
||||||
|
assertNotNull(pm.modules.await()["lyng.foo"])
|
||||||
|
assertIs<ModuleScope>(pm.modules.await()["lyng.foo"]!!.await())
|
||||||
|
|
||||||
|
assertEquals("foo1", pm.modules.await()["lyng.foo"]!!.await().eval("foo()").toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImports2() = runTest() {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
||||||
|
|
||||||
|
val src = """
|
||||||
|
import lyng.foo
|
||||||
|
|
||||||
|
foo()
|
||||||
|
""".trimIndent().toSource("test")
|
||||||
|
|
||||||
|
val scope = ModuleScope(pm, src)
|
||||||
|
assertEquals("foo1", scope.eval(src).toString())
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user