lambda syntax added
This commit is contained in:
1 parent
2344e19857
commit
19a2a1d909
10 files changed
+342
-59
No files matched your search
@@ -24,6 +24,8 @@ data class Arguments(val list: List<Info>) : Iterable<Obj> {
|
||||
|
||||
operator fun get(index: Int): Obj = list[index].value
|
||||
|
||||
val values: List<Obj> by lazy { list.map { it.value } }
|
||||
|
||||
fun firstAndOnly(): Obj {
|
||||
if (list.size != 1) throw IllegalArgumentException("Expected one argument, got ${list.size}")
|
||||
return list.first().value
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.ling.TypeDecl
|
||||
|
||||
/**
|
||||
* The LYNG compiler.
|
||||
*/
|
||||
@@ -28,22 +30,6 @@ class Compiler(
|
||||
val t = cc.next()
|
||||
return when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
// could be keyword, assignment or just the expression
|
||||
// val next = tokens.next()
|
||||
// if (next.type == Token.Type.ASSIGN) {
|
||||
// this _is_ assignment statement
|
||||
// return AssignStatement(
|
||||
// t.pos, t.value,
|
||||
// parseStatement(tokens) ?: throw ScriptError(
|
||||
// t.pos,
|
||||
// "Expecting expression for assignment operator"
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// not assignment, maybe keyword statement:
|
||||
// get back the token which is not '=':
|
||||
// tokens.previous()
|
||||
// try keyword statement
|
||||
parseKeywordStatement(t, cc)
|
||||
?: run {
|
||||
cc.previous()
|
||||
@@ -315,11 +301,11 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
// Token.Type.LBRACE -> {
|
||||
// if( operand != null ) {
|
||||
// throw ScriptError(t.pos, "syntax error: lambda expression not allowed here")
|
||||
// }
|
||||
// }
|
||||
Token.Type.LBRACE -> {
|
||||
if (operand != null) {
|
||||
throw ScriptError(t.pos, "syntax error: lambda expression not allowed here")
|
||||
} else operand = parseLambdaExpression(cc)
|
||||
}
|
||||
|
||||
|
||||
else -> {
|
||||
@@ -331,6 +317,56 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse lambda expression, leading '{' is already consumed
|
||||
*/
|
||||
private fun parseLambdaExpression(cc: CompilerContext): Accessor {
|
||||
// lambda args are different:
|
||||
val startPos = cc.currentPos()
|
||||
val argsDeclaration = parseArgsDeclaration(cc)
|
||||
if (argsDeclaration != null && argsDeclaration.endTokenType != Token.Type.ARROW)
|
||||
throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments")
|
||||
val pos = cc.currentPos()
|
||||
val body = parseBlock(cc, skipLeadingBrace = true)
|
||||
return Accessor { _ ->
|
||||
statement {
|
||||
val context = this.copy(pos)
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.values
|
||||
val itValue: Obj = when (l.size) {
|
||||
// no args: it == void
|
||||
0 -> ObjVoid
|
||||
// one args: it is this arg
|
||||
1 -> l[0]
|
||||
// more args: it is a list of args
|
||||
else -> ObjList(l.toMutableList())
|
||||
}
|
||||
context.addItem("it", false, itValue)
|
||||
} else {
|
||||
// assign vars as declared
|
||||
if( args.size != argsDeclaration.args.size && !argsDeclaration.args.last().isEllipsis)
|
||||
raiseArgumentError("Too many arguments : called with ${args.size}, lambda accepts only ${argsDeclaration.args.size}")
|
||||
for ((n, a) in argsDeclaration.args.withIndex()) {
|
||||
if (n >= args.size) {
|
||||
if (a.initialValue != null)
|
||||
context.addItem(a.name, false, a.initialValue.execute(context))
|
||||
else throw ScriptError(a.pos, "argument $n is out of scope")
|
||||
} else {
|
||||
val value = if( a.isEllipsis) {
|
||||
ObjList(args.values.subList(n, args.values.size).toMutableList())
|
||||
}
|
||||
else
|
||||
args[n]
|
||||
context.addItem(a.name, false, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
body.execute(context)
|
||||
}.asReadonly
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseArrayLiteral(cc: CompilerContext): List<ListEntry> {
|
||||
// it should be called after LBRACKET is consumed
|
||||
val entries = mutableListOf<ListEntry>()
|
||||
@@ -369,6 +405,89 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
data class ArgVar(
|
||||
val name: String,
|
||||
val type: TypeDecl = TypeDecl.Obj,
|
||||
val pos: Pos,
|
||||
val isEllipsis: Boolean,
|
||||
val initialValue: Statement? = null
|
||||
)
|
||||
|
||||
data class ArgsDeclaration(val args: List<ArgVar>, val endTokenType: Token.Type) {
|
||||
init {
|
||||
val i = args.indexOfFirst { it.isEllipsis }
|
||||
if (i >= 0 && i != args.lastIndex) throw ScriptError(args[i].pos, "ellipsis argument must be last")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument declaration, used in lambda (and later in fn too)
|
||||
* @return declaration or null if there is no valid list of arguments
|
||||
*/
|
||||
private fun parseArgsDeclaration(cc: CompilerContext): ArgsDeclaration? {
|
||||
val result = mutableListOf<ArgVar>()
|
||||
var endTokenType: Token.Type? = null
|
||||
val startPos = cc.savePos()
|
||||
|
||||
while (endTokenType == null) {
|
||||
val t = cc.next()
|
||||
when (t.type) {
|
||||
Token.Type.NEWLINE -> {}
|
||||
Token.Type.ID -> {
|
||||
var defaultValue: Statement? = null
|
||||
cc.ifNextIs(Token.Type.ASSIGN) {
|
||||
defaultValue = parseExpression(cc)
|
||||
}
|
||||
// type information
|
||||
val typeInfo = parseTypeDeclaration(cc)
|
||||
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
||||
result += ArgVar(t.value, typeInfo, t.pos, isEllipsis, defaultValue)
|
||||
|
||||
// important: valid argument list continues with ',' and ends with '->' or ')'
|
||||
// otherwise it is not an argument list:
|
||||
when (val tt = cc.next().type) {
|
||||
Token.Type.RPAREN -> {
|
||||
// end of arguments
|
||||
endTokenType = tt
|
||||
}
|
||||
|
||||
Token.Type.ARROW -> {
|
||||
// end of arguments too
|
||||
endTokenType = tt
|
||||
}
|
||||
|
||||
Token.Type.COMMA -> {
|
||||
// next argument, OK
|
||||
}
|
||||
|
||||
else -> {
|
||||
// this is not a valid list of arguments:
|
||||
cc.restorePos(startPos) // for the current
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// if we get here. there os also no valid list of arguments:
|
||||
cc.restorePos(startPos)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
// arg list is valid:
|
||||
checkNotNull(endTokenType)
|
||||
return ArgsDeclaration(result, endTokenType)
|
||||
}
|
||||
|
||||
private fun parseTypeDeclaration(cc: CompilerContext): TypeDecl {
|
||||
val result = TypeDecl.Obj
|
||||
cc.ifNextIs(Token.Type.COLON) {
|
||||
TODO("parse type declaration here")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun parseArgs(cc: CompilerContext): List<ParsedArgument> {
|
||||
val args = mutableListOf<ParsedArgument>()
|
||||
do {
|
||||
@@ -837,16 +956,19 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseBlock(tokens: CompilerContext): Statement {
|
||||
val t = tokens.next()
|
||||
if (t.type != Token.Type.LBRACE)
|
||||
throw ScriptError(t.pos, "Expected block body start: {")
|
||||
val block = parseScript(t.pos, tokens)
|
||||
return statement(t.pos) {
|
||||
private fun parseBlock(cc: CompilerContext, skipLeadingBrace: Boolean = false): Statement {
|
||||
val startPos = cc.currentPos()
|
||||
if( !skipLeadingBrace ) {
|
||||
val t = cc.next()
|
||||
if (t.type != Token.Type.LBRACE)
|
||||
throw ScriptError(t.pos, "Expected block body start: {")
|
||||
}
|
||||
val block = parseScript(startPos, cc)
|
||||
return statement(startPos) {
|
||||
// block run on inner context:
|
||||
block.execute(it.copy(t.pos))
|
||||
block.execute(it.copy(startPos))
|
||||
}.also {
|
||||
val t1 = tokens.next()
|
||||
val t1 = cc.next()
|
||||
if (t1.type != Token.Type.RBRACE)
|
||||
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
|
||||
}
|
||||
@@ -870,7 +992,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
val initialExpression = if (setNull) null else parseStatement(tokens)
|
||||
val initialExpression = if (setNull) null else parseExpression(tokens)
|
||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||
|
||||
return statement(nameToken.pos) { context ->
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
internal class CompilerContext(val tokens: List<Token>) : ListIterator<Token> by tokens.listIterator() {
|
||||
internal class CompilerContext(val tokens: List<Token>) {
|
||||
val labels = mutableSetOf<String>()
|
||||
|
||||
var currentIndex = 0
|
||||
|
||||
fun hasNext() = currentIndex < tokens.size
|
||||
fun hasPrevious() = currentIndex > 0
|
||||
fun next() = tokens.getOrElse(currentIndex) { throw IllegalStateException("No next token") }.also { currentIndex++ }
|
||||
fun previous() = if( !hasPrevious() ) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
||||
|
||||
fun savePos() = currentIndex
|
||||
fun restorePos(pos: Int) { currentIndex = pos }
|
||||
|
||||
fun ensureLabelIsValid(pos: Pos, label: String) {
|
||||
if (label !in labels)
|
||||
throw ScriptError(pos, "Undefined label '$label'")
|
||||
|
||||
@@ -83,6 +83,11 @@ private class Parser(fromPos: Pos) {
|
||||
Token("-", from, Token.Type.MINUSASSIGN)
|
||||
}
|
||||
|
||||
'>' -> {
|
||||
pos.advance()
|
||||
Token("->", from, Token.Type.ARROW)
|
||||
}
|
||||
|
||||
else -> Token("-", from, Token.Type.MINUS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ class Script(
|
||||
addConst("Char", ObjChar.type)
|
||||
addConst("List", ObjList.type)
|
||||
addConst("Range", ObjRange.type)
|
||||
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
addConst("Callable", Statement.type)
|
||||
// interfaces
|
||||
addConst("Iterable", ObjIterable)
|
||||
addConst("Array", ObjArray)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
sealed class TypeDecl {
|
||||
object Obj : TypeDecl()
|
||||
}
|
||||
@@ -15,6 +15,8 @@ abstract class Statement(
|
||||
val returnType: ObjType = ObjType.Any
|
||||
) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
abstract val pos: Pos
|
||||
abstract suspend fun execute(context: Context): Obj
|
||||
|
||||
@@ -28,6 +30,10 @@ abstract class Statement(
|
||||
|
||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Callable")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Statement.raise(text: String): Nothing {
|
||||
|
||||
@@ -1040,14 +1040,75 @@ class ScriptTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambda1() = runTest {
|
||||
val l = eval("""
|
||||
fun testLambdaWithIt1() = runTest {
|
||||
eval("""
|
||||
val x = {
|
||||
122
|
||||
it + "!"
|
||||
}
|
||||
x
|
||||
val y = if( 4 < 3 ) "NG" else "OK"
|
||||
assert( x::class == Callable)
|
||||
assert( x is Callable)
|
||||
assert(y == "OK")
|
||||
assert( x("hello") == "hello!")
|
||||
""".trimIndent())
|
||||
println(l)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithIt2() = runTest {
|
||||
eval("""
|
||||
val x = {
|
||||
assert(it == void)
|
||||
}
|
||||
assert( x() == void)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithIt3() = runTest {
|
||||
eval("""
|
||||
val x = {
|
||||
assert( it == [1,2,"end"])
|
||||
}
|
||||
println("0----")
|
||||
assert( x(1, 2, "end") == void)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithArgs() = runTest {
|
||||
eval("""
|
||||
val x = { x, y, z ->
|
||||
assert( [x, y, z] == [1,2,"end"])
|
||||
}
|
||||
assert( x(1, 2, "end") == void)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithArgsEllipsis() = runTest {
|
||||
eval("""
|
||||
val x = { x, y... ->
|
||||
println("-- y=",y)
|
||||
println(":: "+y::class)
|
||||
assert( [x, ...y] == [1,2,"end"])
|
||||
}
|
||||
assert( x(1, 2, "end") == void)
|
||||
assert( x(1, ...[2, "end"]) == void)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithBadArgs() = runTest {
|
||||
assertFails {
|
||||
eval(
|
||||
"""
|
||||
val x = { x, y ->
|
||||
void
|
||||
}
|
||||
assert( x(1, 2) == void)
|
||||
assert( x(1, ...[2, "end"]) == void)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user