+fn definitions and calls, closures

This commit is contained in:
Sergey Chernov 2025-05-18 14:41:01 +04:00
parent 408543d191
commit 166b1fa0d5
5 changed files with 174 additions and 48 deletions

View File

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

View File

@ -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
@ -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 -> {
@ -185,33 +190,44 @@ class Compiler {
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,13 +263,89 @@ 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)

View File

@ -185,8 +185,4 @@ private class Parser(fromPos: Pos) {
} }
private fun advance() = pos.advance() private fun advance() = pos.advance()
init {
// advance()
}
} }

View File

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

View File

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