mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-09 00:34:07 +02:00
Lambda
This commit is contained in:
parent
d93fb1fdfc
commit
806184aa5d
src/com/maddyhome/idea/vim/vimscript
model
parser/visitors
test/org/jetbrains/plugins/ideavim
vimscript-info
@ -50,7 +50,7 @@ data class VimFuncref(
|
||||
|
||||
override fun toString(): String {
|
||||
return when (type) {
|
||||
Type.LAMBDA -> "function('<lambda>${this.definition.name}')"
|
||||
Type.LAMBDA -> "function('${this.definition.name}')"
|
||||
Type.FUNCREF -> "function('${this.definition.name}')"
|
||||
Type.FUNCTION -> this.definition.name
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2021 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
import com.maddyhome.idea.vim.vimscript.model.Executable
|
||||
import com.maddyhome.idea.vim.vimscript.model.commands.LetCommand
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.AssignmentOperator
|
||||
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
|
||||
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionFlag
|
||||
import com.maddyhome.idea.vim.vimscript.model.statements.ReturnStatement
|
||||
|
||||
data class LambdaExpression(val args: List<String>, val expr: Expression) : Expression() {
|
||||
|
||||
override fun evaluate(editor: Editor, context: DataContext, parent: Executable): VimFuncref {
|
||||
val function = FunctionDeclaration(null, getFunctionName(), args, buildBody(), false, setOf(FunctionFlag.CLOSURE), false)
|
||||
function.parent = parent
|
||||
return VimFuncref(function, VimList(mutableListOf()), VimDictionary(LinkedHashMap()), VimFuncref.Type.LAMBDA, false)
|
||||
}
|
||||
|
||||
private fun getFunctionName(): String {
|
||||
return "<lambda>" + VimFuncref.lambdaCounter++
|
||||
}
|
||||
|
||||
private fun buildBody(): List<Executable> {
|
||||
val body = mutableListOf<Executable>()
|
||||
for (argument in args) {
|
||||
body.add(LetCommand(Ranges(), Variable(Scope.LOCAL_VARIABLE, argument), AssignmentOperator.ASSIGNMENT, Variable(Scope.FUNCTION_VARIABLE, argument)))
|
||||
}
|
||||
body.add(ReturnStatement(expr))
|
||||
return body
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2021 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.vimscript.model.expressions
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.vimscript.model.Executable
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||
|
||||
class LambdaFunctionCallExpression(val lambda: LambdaExpression, val arguments: List<Expression>) : Expression() {
|
||||
|
||||
override fun evaluate(editor: Editor, context: DataContext, parent: Executable): VimDataType {
|
||||
val funcref = lambda.evaluate(editor, context, parent)
|
||||
return funcref.execute(arguments, editor, context, parent)
|
||||
}
|
||||
}
|
@ -197,11 +197,26 @@ object ExpressionVisitor : VimscriptBaseVisitor<Expression>() {
|
||||
if (ctx.functionScope() != null) {
|
||||
scope = Scope.getByValue(ctx.functionScope().text)
|
||||
}
|
||||
val functionArguments = ctx.functionArguments().expr()
|
||||
.mapNotNull { visit(it) }
|
||||
val functionArguments = ctx.functionArguments().expr().mapNotNull { visit(it) }.toMutableList()
|
||||
return FunctionCallExpression(scope, functionName, functionArguments)
|
||||
}
|
||||
|
||||
override fun visitLambdaFunctionCallExpression(ctx: VimscriptParser.LambdaFunctionCallExpressionContext): LambdaFunctionCallExpression {
|
||||
val lambda = visitLambda(ctx.lambda())
|
||||
val arguments = ctx.functionArguments().expr().mapNotNull { visit(it) }
|
||||
return LambdaFunctionCallExpression(lambda, arguments)
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(ctx: VimscriptParser.LambdaExpressionContext?): Expression {
|
||||
return super.visitLambdaExpression(ctx)
|
||||
}
|
||||
|
||||
override fun visitLambda(ctx: VimscriptParser.LambdaContext): LambdaExpression {
|
||||
val arguments = ctx.argumentsDeclaration().variableName().map { it.text }
|
||||
val expr = visit(ctx.expr())
|
||||
return LambdaExpression(arguments, expr)
|
||||
}
|
||||
|
||||
override fun visitSublistExpression(ctx: SublistExpressionContext): Expression {
|
||||
val ex = visit(ctx.expr(0))
|
||||
val from = if (ctx.from != null) visit(ctx.from) else null
|
||||
|
@ -68,6 +68,7 @@ import com.maddyhome.idea.vim.option.OptionsManager.ideastrictmode
|
||||
import com.maddyhome.idea.vim.option.OptionsManager.resetAllOptions
|
||||
import com.maddyhome.idea.vim.option.ToggleOption
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
|
||||
import com.maddyhome.idea.vim.vimscript.parser.errors.IdeavimErrorListener
|
||||
import com.maddyhome.idea.vim.vimscript.services.VariableService
|
||||
import org.assertj.core.api.Assertions
|
||||
@ -126,6 +127,7 @@ abstract class VimTestCase : UsefulTestCase() {
|
||||
SelectionVimListenerSuppressor.lock().use { myFixture.tearDown() }
|
||||
ExEntryPanel.getInstance().deactivate(false)
|
||||
VariableService.clear()
|
||||
VimFuncref.lambdaCounter = 0
|
||||
IdeavimErrorListener.testLogger.clear()
|
||||
VimPlugin.getRegister().resetRegisters()
|
||||
VimPlugin.getSearch().resetState()
|
||||
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2021 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.ex.implementation.expressions
|
||||
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
|
||||
class LambdaTest : VimTestCase() {
|
||||
|
||||
fun `test lambda with no args`() {
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
let L = {-> "hello from an empty function"} |
|
||||
echo L()
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertExOutput("hello from an empty function\n")
|
||||
}
|
||||
|
||||
fun `test lambda with one arg`() {
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
let L = { x -> x + 10 } |
|
||||
echo L(32)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertExOutput("42\n")
|
||||
}
|
||||
|
||||
fun `test lambda with multiple arguments`() {
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
let Addition = { x, y -> x + y } |
|
||||
echo Addition(-142, 184)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertExOutput("42\n")
|
||||
}
|
||||
|
||||
fun `test lambda's name`() {
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
let Addition = { x, y -> x + y } |
|
||||
let Subtraction = { x, y -> x - y} |
|
||||
echo Addition
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertExOutput("function('<lambda>0')\n")
|
||||
|
||||
typeText(commandToKeys("echo Subtraction"))
|
||||
assertExOutput("function('<lambda>1')\n")
|
||||
}
|
||||
|
||||
fun `test lambda is a closure function`() {
|
||||
// in this test we test that we can access outer function variables from lambda
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
function! Subtraction(minuend, subtrahend) |
|
||||
let Subtract = { x, y -> x - y} |
|
||||
echo a:minuend .. ' - ' .. a:subtrahend .. ' = ' .. Subtract(a:minuend, a:subtrahend) |
|
||||
endfunction |
|
||||
call Subtraction(5, 2)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertExOutput("5 - 2 = 3\n")
|
||||
}
|
||||
|
||||
// todo maybe create new lambda handler after refactoring?..
|
||||
// redundant args should be ignored
|
||||
// fun `test lambda with more arguments than needed`() {
|
||||
// configureByText("\n")
|
||||
// typeText(
|
||||
// commandToKeys(
|
||||
// """
|
||||
// let L = { x -> x + 10 } |
|
||||
// echo L(32, 100)
|
||||
// """.trimIndent()
|
||||
// )
|
||||
// )
|
||||
// assertPluginError(false)
|
||||
// assertExOutput("42\n")
|
||||
// }
|
||||
|
||||
@TestWithoutNeovim(SkipNeovimReason.PLUGIN_ERROR)
|
||||
fun `test lambda with less arguments than needed`() {
|
||||
configureByText("\n")
|
||||
typeText(
|
||||
commandToKeys(
|
||||
"""
|
||||
let L = { x -> x + 10 } |
|
||||
echo L()
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E119: Not enough arguments for function: <lambda>0")
|
||||
}
|
||||
|
||||
fun `test lambda function call with no args`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("echo {-> 'hello from an empty function'}()"))
|
||||
assertExOutput("hello from an empty function\n")
|
||||
}
|
||||
|
||||
fun `test lambda function call with args`() {
|
||||
configureByText("\n")
|
||||
typeText(commandToKeys("echo {x, y -> x*y}(6, 7)"))
|
||||
assertExOutput("42\n")
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2021 The IdeaVim authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.jetbrains.plugins.ideavim.ex.parser.expressions
|
||||
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.BinExpression
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.LambdaExpression
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.LambdaFunctionCallExpression
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.BinaryOperator
|
||||
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
|
||||
import org.junit.experimental.theories.DataPoints
|
||||
import org.junit.experimental.theories.Theories
|
||||
import org.junit.experimental.theories.Theory
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(Theories::class)
|
||||
class LambdaTests {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val values = listOf("", " ") @DataPoints get
|
||||
}
|
||||
|
||||
@Theory
|
||||
fun `lambda with no args test`(sp1: String, sp2: String, sp3: String) {
|
||||
val lambdaExpression = VimscriptParser.parseExpression("{$sp1->$sp2'error'$sp3}") as LambdaExpression
|
||||
assertEquals(0, lambdaExpression.args.size)
|
||||
assertEquals(SimpleExpression(VimString("error")), lambdaExpression.expr)
|
||||
}
|
||||
|
||||
@Theory
|
||||
fun `lambda with multiple args test`(sp1: String, sp2: String, sp3: String, sp4: String, sp5: String, sp6: String) {
|
||||
val lambdaExpression = VimscriptParser.parseExpression("{${sp1}a$sp2,${sp3}b$sp4->${sp5}a+b$sp6}") as LambdaExpression
|
||||
assertEquals(listOf("a", "b"), lambdaExpression.args)
|
||||
assertEquals(BinExpression(Variable(null, "a"), Variable(null, "b"), BinaryOperator.ADDITION), lambdaExpression.expr)
|
||||
}
|
||||
|
||||
@Theory
|
||||
fun `lambda function call with no args test`() {
|
||||
val functionCall = VimscriptParser.parseExpression("{->'error'}()") as LambdaFunctionCallExpression
|
||||
assertEquals(0, functionCall.arguments.size)
|
||||
assertEquals(0, functionCall.lambda.args.size)
|
||||
assertEquals(SimpleExpression(VimString("error")), functionCall.lambda.expr)
|
||||
}
|
||||
|
||||
@Theory
|
||||
fun `lambda function call with multiple args test`(sp1: String, sp2: String, sp3: String, sp4: String) {
|
||||
val functionCall = VimscriptParser.parseExpression("{->'error'}(${sp1}a$sp2,${sp3}b$sp4)") as LambdaFunctionCallExpression
|
||||
assertEquals(2, functionCall.arguments.size)
|
||||
assertEquals(listOf(Variable(null, "a"), Variable(null, "b")), functionCall.arguments)
|
||||
assertEquals(0, functionCall.lambda.args.size)
|
||||
assertEquals(SimpleExpression(VimString("error")), functionCall.lambda.expr)
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
- [x] `call` command
|
||||
- [x] optional arguments `...`
|
||||
- [x] funcref type
|
||||
- [ ] lambdas
|
||||
- [x] lambdas
|
||||
- [ ] function as method
|
||||
- [ ] `function` function
|
||||
- [ ] `funcref` function
|
||||
|
Loading…
Reference in New Issue
Block a user