1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-09 00:34:07 +02:00
This commit is contained in:
lippfi 2021-10-01 02:38:12 +03:00
parent d93fb1fdfc
commit 806184aa5d
8 changed files with 322 additions and 4 deletions
src/com/maddyhome/idea/vim/vimscript
test/org/jetbrains/plugins/ideavim
VimTestCase.kt
ex
implementation/expressions
parser/expressions
vimscript-info

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@
- [x] `call` command
- [x] optional arguments `...`
- [x] funcref type
- [ ] lambdas
- [x] lambdas
- [ ] function as method
- [ ] `function` function
- [ ] `funcref` function