+fn definitions and calls, closures
This commit is contained in:
parent
408543d191
commit
166b1fa0d5
@ -1,17 +1,19 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
data class Arguments(val list: List<Obj> ) {
|
data class Arguments(val callerPos: Pos,val list: List<Info>) {
|
||||||
|
|
||||||
|
data class Info(val value: Obj,val pos: Pos)
|
||||||
|
|
||||||
val size by list::size
|
val size by list::size
|
||||||
|
|
||||||
operator fun get(index: Int): Obj = list[index]
|
operator fun get(index: Int): Obj = list[index].value
|
||||||
|
|
||||||
fun firstAndOnly(): Obj {
|
fun firstAndOnly(): Obj {
|
||||||
if( list.size != 1 ) throw IllegalArgumentException("Expected one argument, got ${list.size}")
|
if( list.size != 1 ) throw IllegalArgumentException("Expected one argument, got ${list.size}")
|
||||||
return list.first()
|
return list.first().value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
val EMPTY = Arguments("".toSource().startPos,emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,10 +48,10 @@ type alias has target type name. So we have to have something that denotes a _ty
|
|||||||
class Compiler {
|
class Compiler {
|
||||||
|
|
||||||
fun compile(source: Source): Script {
|
fun compile(source: Source): Script {
|
||||||
val tokens = parseLing(source).listIterator()
|
return parseScript(source.startPos, parseLing(source).listIterator())
|
||||||
val start = source.startPos
|
}
|
||||||
// at this level: "global" context: just script to execute or new function
|
|
||||||
// declaration forming closures
|
private fun parseScript(start: Pos, tokens: ListIterator<Token>): Script {
|
||||||
val statements = mutableListOf<Statement>()
|
val statements = mutableListOf<Statement>()
|
||||||
while (parseStatement(tokens)?.also {
|
while (parseStatement(tokens)?.also {
|
||||||
statements += it
|
statements += it
|
||||||
@ -61,7 +61,7 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatement(tokens: ListIterator<Token>): Statement? {
|
private fun parseStatement(tokens: ListIterator<Token>): Statement? {
|
||||||
while(true) {
|
while (true) {
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
@ -90,6 +90,11 @@ class Compiler {
|
|||||||
|
|
||||||
Token.Type.SEMICOLON -> continue
|
Token.Type.SEMICOLON -> continue
|
||||||
|
|
||||||
|
Token.Type.RBRACE -> {
|
||||||
|
tokens.previous()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.EOF -> null
|
Token.Type.EOF -> null
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@ -167,51 +172,62 @@ class Compiler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseVarAccess(id: Token, tokens: ListIterator<Token>,path: List<String> = emptyList()): Statement {
|
fun parseVarAccess(id: Token, tokens: ListIterator<Token>, path: List<String> = emptyList()): Statement {
|
||||||
val nt = tokens.next()
|
val nt = tokens.next()
|
||||||
|
|
||||||
fun resolve(context: Context): Context {
|
fun resolve(context: Context): Context {
|
||||||
var targetContext = context
|
var targetContext = context
|
||||||
for( n in path) {
|
for (n in path) {
|
||||||
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
val x = targetContext[n] ?: throw ScriptError(id.pos, "undefined symbol: $n")
|
||||||
(x.value as? ObjNamespace )?.let { targetContext = it.context }
|
(x.value as? ObjNamespace)?.let { targetContext = it.context }
|
||||||
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
?: throw ScriptError(id.pos, "Invalid symbolic path (wrong type of ${x.name}: ${x.value}")
|
||||||
}
|
}
|
||||||
return targetContext
|
return targetContext
|
||||||
}
|
}
|
||||||
return when(nt.type) {
|
return when (nt.type) {
|
||||||
Token.Type.DOT -> {
|
Token.Type.DOT -> {
|
||||||
// selector
|
// selector
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
if( t.type== Token.Type.ID) {
|
if (t.type == Token.Type.ID) {
|
||||||
parseVarAccess(t,tokens,path+id.value)
|
parseVarAccess(t, tokens, path + id.value)
|
||||||
}
|
} else
|
||||||
else
|
throw ScriptError(t.pos, "Expected identifier after '.'")
|
||||||
throw ScriptError(t.pos,"Expected identifier after '.'")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LPAREN -> {
|
Token.Type.LPAREN -> {
|
||||||
|
// function call
|
||||||
// Load arg list
|
// Load arg list
|
||||||
val args = mutableListOf<Statement>()
|
val args = mutableListOf<Arguments.Info>()
|
||||||
do {
|
do {
|
||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
when(t.type) {
|
when (t.type) {
|
||||||
Token.Type.RPAREN, Token.Type.COMMA -> {}
|
Token.Type.RPAREN, Token.Type.COMMA -> {}
|
||||||
else -> {
|
else -> {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
parseExpression(tokens)?.let { args += it }
|
parseExpression(tokens)?.let { args += Arguments.Info(it, t.pos) }
|
||||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (t.type != Token.Type.RPAREN)
|
} while (t.type != Token.Type.RPAREN)
|
||||||
|
|
||||||
statement(id.pos) { context ->
|
statement(id.pos) { context ->
|
||||||
val v = resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: $id")
|
val v = resolve(context).get(id.value) ?: throw ScriptError(id.pos, "Undefined variable: $id")
|
||||||
(v.value as? Statement)?.execute(context.copy(Arguments(args.map { it.execute(context) })))
|
(v.value as? Statement)?.execute(
|
||||||
|
context.copy(
|
||||||
|
Arguments(
|
||||||
|
nt.pos,
|
||||||
|
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
|
?: throw ScriptError(id.pos, "Variable $id is not callable ($id)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LBRACKET -> {
|
Token.Type.LBRACKET -> {
|
||||||
TODO("indexing")
|
TODO("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// just access the var
|
// just access the var
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
@ -247,32 +263,108 @@ class Compiler {
|
|||||||
* Parse keyword-starting statenment.
|
* Parse keyword-starting statenment.
|
||||||
* @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, tokens: ListIterator<Token>): Statement?
|
private fun parseKeywordStatement(id: Token, tokens: ListIterator<Token>): Statement? = when (id.value) {
|
||||||
= when (id.value) {
|
|
||||||
"val" -> parseVarDeclaration(id.value, false, tokens)
|
"val" -> parseVarDeclaration(id.value, false, tokens)
|
||||||
"var" -> parseVarDeclaration(id.value, true, tokens)
|
"var" -> parseVarDeclaration(id.value, true, tokens)
|
||||||
|
"fn", "fun" -> parseFunctionDeclaration(tokens)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class FnParamDef(
|
||||||
|
val name: String,
|
||||||
|
val pos: Pos,
|
||||||
|
val defaultValue: Statement? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun parseFunctionDeclaration(tokens: ListIterator<Token>): Statement {
|
||||||
|
var t = tokens.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()
|
||||||
|
if (t.type != Token.Type.LPAREN)
|
||||||
|
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
|
||||||
|
val params = mutableListOf<FnParamDef>()
|
||||||
|
var defaultListStarted = false
|
||||||
|
do {
|
||||||
|
t = tokens.next()
|
||||||
|
if (t.type == Token.Type.RPAREN)
|
||||||
|
break
|
||||||
|
if (t.type != Token.Type.ID)
|
||||||
|
throw ScriptError(t.pos, "Expected identifier after '('")
|
||||||
|
val n = tokens.next()
|
||||||
|
val defaultValue = if (n.type == Token.Type.ASSIGN) {
|
||||||
|
parseExpression(tokens)?.also { defaultListStarted = true }
|
||||||
|
?: throw ScriptError(n.pos, "Expected initialization expression")
|
||||||
|
} else {
|
||||||
|
if (defaultListStarted)
|
||||||
|
throw ScriptError(n.pos, "requires default value too")
|
||||||
|
if (n.type != Token.Type.COMMA)
|
||||||
|
tokens.previous()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
params.add(FnParamDef(t.value, t.pos, defaultValue))
|
||||||
|
} while (true)
|
||||||
|
|
||||||
|
println("arglist: $params")
|
||||||
|
|
||||||
|
// Here we should be at open body
|
||||||
|
val fnStatements = parseBlock(tokens)
|
||||||
|
|
||||||
|
val fnBody = statement(t.pos) { context ->
|
||||||
|
// load params
|
||||||
|
for ((i, d) in params.withIndex()) {
|
||||||
|
if (i < context.args.size)
|
||||||
|
context.addItem(d.name, false, context.args.list[i].value)
|
||||||
|
else
|
||||||
|
context.addItem(
|
||||||
|
d.name,
|
||||||
|
false,
|
||||||
|
d.defaultValue?.execute(context)
|
||||||
|
?: throw ScriptError(context.args.callerPos, "missing required argument #${1+i}: ${d.name}")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnStatements.execute(context)
|
||||||
|
}
|
||||||
|
return statement(start) { context ->
|
||||||
|
context.addItem(name, false, fnBody)
|
||||||
|
fnBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseBlock(tokens: ListIterator<Token>): Statement {
|
||||||
|
val t = tokens.next()
|
||||||
|
if (t.type != Token.Type.LBRACE)
|
||||||
|
throw ScriptError(t.pos, "Expected block body start: {")
|
||||||
|
return parseScript(t.pos, tokens).also {
|
||||||
|
val t1 = tokens.next()
|
||||||
|
if (t1.type != Token.Type.RBRACE)
|
||||||
|
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: ListIterator<Token>): Statement {
|
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: ListIterator<Token>): Statement {
|
||||||
val nameToken = tokens.next()
|
val nameToken = tokens.next()
|
||||||
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 after '$kind'")
|
||||||
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 (!mutable)
|
||||||
throw ScriptError(eqToken.pos, "Expected initializator: '=' after '$kind ${name}'")
|
throw ScriptError(eqToken.pos, "Expected initializator: '=' after '$kind ${name}'")
|
||||||
else {
|
else {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
setNull = true
|
setNull = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val initialExpression = if( setNull ) null else parseExpression(tokens)
|
val initialExpression = if (setNull) null else parseExpression(tokens)
|
||||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
return statement(nameToken.pos) { context ->
|
return statement(nameToken.pos) { context ->
|
||||||
if( context.containsLocal(name) )
|
if (context.containsLocal(name))
|
||||||
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
||||||
val initValue = initialExpression?.execute(context) ?: ObjNull
|
val initValue = initialExpression?.execute(context) ?: ObjNull
|
||||||
context.addItem(name, mutable, initValue)
|
context.addItem(name, mutable, initValue)
|
||||||
|
@ -185,8 +185,4 @@ private class Parser(fromPos: Pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun advance() = pos.advance()
|
private fun advance() = pos.advance()
|
||||||
|
|
||||||
init {
|
|
||||||
// advance()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -39,8 +39,10 @@ class MutablePos(private val from: Pos) {
|
|||||||
return if (column+1 < current.length) {
|
return if (column+1 < current.length) {
|
||||||
current[column++]
|
current[column++]
|
||||||
} else {
|
} else {
|
||||||
line++
|
|
||||||
column = 0
|
column = 0
|
||||||
|
while( ++line < lines.size && lines[line].isEmpty() ) {
|
||||||
|
// skip empty lines
|
||||||
|
}
|
||||||
if(line >= lines.size) null
|
if(line >= lines.size) null
|
||||||
else lines[line][column]
|
else lines[line][column]
|
||||||
}
|
}
|
||||||
@ -59,4 +61,7 @@ class MutablePos(private val from: Pos) {
|
|||||||
|
|
||||||
override fun toString() = "($line:$column)"
|
override fun toString() = "($line:$column)"
|
||||||
|
|
||||||
|
init {
|
||||||
|
if( lines[0].isEmpty()) advance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,14 +122,45 @@ class ScriptTest {
|
|||||||
assertEquals(10, context.eval("b").toInt())
|
assertEquals(10, context.eval("b").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
// fun functionTest() = runTest {
|
fun functionTest() = runTest {
|
||||||
// val context = Context()
|
val context = Context()
|
||||||
// context.eval("""
|
context.eval(
|
||||||
// fun foo(a, b) {
|
"""
|
||||||
// a + b
|
fun foo(a, b) {
|
||||||
// }
|
a + b
|
||||||
// """.trimIndent())
|
}
|
||||||
// }
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals(17, context.eval("foo(3,14)").toInt())
|
||||||
|
assertFailsWith<ScriptError> {
|
||||||
|
assertEquals(17, context.eval("foo(3)").toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
context.eval("""
|
||||||
|
fn bar(a, b=10) {
|
||||||
|
a + b + 1
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
assertEquals(10, context.eval("bar(3, 6)").toInt())
|
||||||
|
assertEquals(14, context.eval("bar(3)").toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleClosureTest() = runTest {
|
||||||
|
val context = Context()
|
||||||
|
context.eval(
|
||||||
|
"""
|
||||||
|
var global = 10
|
||||||
|
|
||||||
|
fun foo(a, b) {
|
||||||
|
global + a + b
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals(27, context.eval("foo(3,14)").toInt())
|
||||||
|
context.eval("global = 20")
|
||||||
|
assertEquals(37, context.eval("foo(3,14)").toInt())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user