1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-07 13:34:02 +02:00

Optional function arguments

This commit is contained in:
lippfi 2021-10-08 04:13:24 +03:00
parent a3b2b4920a
commit 9e62636059
8 changed files with 89 additions and 9 deletions
src
com/maddyhome/idea/vim/vimscript
main/antlr
test/org/jetbrains/plugins/ideavim/ex/implementation/statements
vimscript-info

View File

@ -34,7 +34,7 @@ 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), true)
val function = FunctionDeclaration(null, getFunctionName(), args, listOf(), buildBody(), false, setOf(FunctionFlag.CLOSURE), true)
function.parent = parent
return VimFuncref(DefinedFunctionHandler(function), VimList(mutableListOf()), null, VimFuncref.Type.LAMBDA)
}

View File

@ -44,7 +44,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
override val name = function.name
override val scope = function.scope
override val minimumNumberOfArguments = function.args.size
override val maximumNumberOfArguments get() = if (function.hasOptionalArguments) null else function.args.size
override val maximumNumberOfArguments get() = if (function.hasOptionalArguments) null else function.args.size + function.defaultArgs.size
override fun doFunction(argumentValues: List<Expression>, editor: Editor, context: DataContext, parent: Executable): VimDataType {
var returnValue: VimDataType? = null
@ -128,6 +128,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
}
private fun initializeFunctionVariables(argumentValues: List<Expression>, editor: Editor, context: DataContext) {
// non-optional function arguments
for ((index, name) in function.args.withIndex()) {
VariableService.storeVariable(
Variable(Scope.FUNCTION_VARIABLE, name),
@ -137,10 +138,30 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
function
)
}
// optional function arguments with default values
for (index in 0 until function.defaultArgs.size) {
val expressionToStore = if (index + function.args.size < argumentValues.size) argumentValues[index + function.args.size] else function.defaultArgs[index].second
VariableService.storeVariable(
Variable(Scope.FUNCTION_VARIABLE, function.defaultArgs[index].first),
expressionToStore.evaluate(editor, context, function),
editor,
context,
function
)
}
// all the other optional arguments passed to function are stored in a:000 variable
if (function.hasOptionalArguments) {
val remainingArgs = if (function.args.size + function.defaultArgs.size < argumentValues.size) {
VimList(
argumentValues.subList(function.args.size + function.defaultArgs.size, argumentValues.size)
.map { it.evaluate(editor, context, function) }.toMutableList()
)
} else {
VimList(mutableListOf())
}
VariableService.storeVariable(
Variable(Scope.FUNCTION_VARIABLE, "000"),
VimList(argumentValues.subList(function.args.size, argumentValues.size).map { it.evaluate(editor, context, function) }.toMutableList()),
remainingArgs,
editor,
context,
function

View File

@ -27,6 +27,7 @@ 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.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.OneElementSublistExpression
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
@ -34,6 +35,7 @@ import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
data class AnonymousFunctionDeclaration(
val sublist: OneElementSublistExpression,
val args: List<String>,
val defaultArgs: List<Pair<String, Expression>>,
val body: List<Executable>,
val replaceExisting: Boolean,
val flags: Set<FunctionFlag>,
@ -55,7 +57,7 @@ data class AnonymousFunctionDeclaration(
throw ExException("E718: Funcref required")
}
}
val declaration = FunctionDeclaration(null, VimFuncref.anonymousCounter++.toString(), args, body, replaceExisting, flags + FunctionFlag.DICT, hasOptionalArguments)
val declaration = FunctionDeclaration(null, VimFuncref.anonymousCounter++.toString(), args, defaultArgs, body, replaceExisting, flags + FunctionFlag.DICT, hasOptionalArguments)
container.dictionary[index] = VimFuncref(DefinedFunctionHandler(declaration), VimList(mutableListOf()), container, VimFuncref.Type.FUNCREF)
return ExecutionResult.Success
}

View File

@ -6,6 +6,7 @@ import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.services.FunctionStorage
@ -13,6 +14,7 @@ data class FunctionDeclaration(
val scope: Scope?,
val name: String,
val args: List<String>,
val defaultArgs: List<Pair<String, Expression>>,
val body: List<Executable>,
val replaceExisting: Boolean,
val flags: Set<FunctionFlag>,

View File

@ -74,6 +74,8 @@ object ExecutableVisitor : VimscriptBaseVisitor<Executable>() {
val functionScope = if (ctx.functionScope() != null) Scope.getByValue(ctx.functionScope().text) else null
val functionName = ctx.functionName().text
val args = ctx.argumentsDeclaration().variableName().map { it.text }
val defaultArgs = ctx.argumentsDeclaration().defaultValue()
.map { Pair<String, Expression>(it.variableName().text, ExpressionVisitor.visit(it.expr())) }
val body = ctx.blockMember().mapNotNull { visitBlockMember(it) }
val replaceExisting = ctx.replace != null
val flags = mutableSetOf<FunctionFlag?>()
@ -81,12 +83,14 @@ object ExecutableVisitor : VimscriptBaseVisitor<Executable>() {
for (flag in ctx.functionFlag()) {
flags.add(FunctionFlag.getByName(flag.text))
}
return FunctionDeclaration(functionScope, functionName, args, body, replaceExisting, flags.filterNotNull().toSet(), hasOptionalArguments)
return FunctionDeclaration(functionScope, functionName, args, defaultArgs, body, replaceExisting, flags.filterNotNull().toSet(), hasOptionalArguments)
}
override fun visitDictFunctionDefinition(ctx: VimscriptParser.DictFunctionDefinitionContext): Executable {
val functionScope = if (ctx.functionScope() != null) Scope.getByValue(ctx.functionScope().text) else null
val args = ctx.argumentsDeclaration().variableName().map { it.text }
val defaultArgs = ctx.argumentsDeclaration().defaultValue()
.map { Pair<String, Expression>(it.variableName().text, ExpressionVisitor.visit(it.expr())) }
val body = ctx.blockMember().mapNotNull { visitBlockMember(it) }
val replaceExisting = ctx.replace != null
val flags = mutableSetOf<FunctionFlag?>()
@ -98,7 +102,7 @@ object ExecutableVisitor : VimscriptBaseVisitor<Executable>() {
for (i in 2 until ctx.literalDictionaryKey().size) {
sublistExpression = OneElementSublistExpression(SimpleExpression(VimString(ctx.literalDictionaryKey(i).text)), sublistExpression)
}
return AnonymousFunctionDeclaration(sublistExpression, args, body, replaceExisting, flags.filterNotNull().toSet(), hasOptionalArguments)
return AnonymousFunctionDeclaration(sublistExpression, args, defaultArgs, body, replaceExisting, flags.filterNotNull().toSet(), hasOptionalArguments)
}
override fun visitTryStatement(ctx: VimscriptParser.TryStatementContext): Executable {

View File

@ -79,7 +79,8 @@ dictFunctionDefinition:
ws_cols ENDFUNCTION WS* (comment | statementSeparator)
;
functionFlag: RANGE | ABORT | DICT | CLOSURE;
argumentsDeclaration: (variableName (WS* COMMA WS* variableName)* (WS* COMMA WS* ETC WS*)? WS*)?;
argumentsDeclaration: (variableName (WS* COMMA WS* variableName)* defaultValue* (WS* COMMA WS* ETC WS*)? WS*)?;
defaultValue: WS* COMMA WS* variableName WS* ASSIGN WS* expr;
augroup: ws_cols AUGROUP ~(NEW_LINE | BAR)* statementSeparator
blockMember*
@ -444,6 +445,7 @@ binaryOperator4: AMPERSAND AMPERSAND;
binaryOperator5: LOGICAL_OR;
register: AT (DIGIT | alphabeticChar | MINUS | COLON | DOT | MOD | NUM | ASSIGN | STAR | PLUS | TILDE | UNDERSCORE | DIV | AT);
// todo argumentDeclaration but without default values
lambda: L_CURLY WS* argumentsDeclaration WS* ARROW WS* expr WS* R_CURLY;
variable: (variableScope COLON)? variableName;

View File

@ -559,5 +559,54 @@ class FunctionDeclarationTest : VimTestCase() {
)
)
assertExOutput("[42, 'optional arg']\n")
typeText(commandToKeys("delfunction! GetOptionalArgs"))
}
fun `test arguments with default values`() {
configureByText("\n")
typeText(
commandToKeys(
"" +
"function GetOptionalArgs(name, a = 10, b = 20) |" +
" return 'a = ' .. a:a .. ', b = ' .. a:b | " +
"endfunction"
)
)
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional')"))
assertExOutput("a = 10, b = 20\n")
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional', 42)"))
assertExOutput("a = 42, b = 20\n")
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional', 100, 200)"))
assertExOutput("a = 100, b = 200\n")
typeText(commandToKeys("delfunction! GetOptionalArgs"))
}
fun `test arguments with default values and optional args`() {
configureByText("\n")
typeText(
commandToKeys(
"" +
"function GetOptionalArgs(name, a = 10, b = 20, ...) |" +
" return {'a': a:a, 'b': a:b, '000': a:000} | " +
"endfunction"
)
)
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional')"))
assertExOutput("{'a': 10, 'b': 20, '000': []}\n")
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional', 42)"))
assertExOutput("{'a': 42, 'b': 20, '000': []}\n")
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional', 100, 200)"))
assertExOutput("{'a': 100, 'b': 200, '000': []}\n")
typeText(commandToKeys("echo GetOptionalArgs('this arg is not optional', 100, 200, 300)"))
assertExOutput("{'a': 100, 'b': 200, '000': [300]}\n")
typeText(commandToKeys("delfunction! GetOptionalArgs"))
}
}

View File

@ -29,10 +29,10 @@
- [x] `funcref` function
- [x] `dict` function flag
- [x] anonymous functions
- [ ] default value in functions e.g. `function F1(a, b = 10)`
- [ ] delayed parsing of if/for/while etc.
- [x] default value in functions e.g. `function F1(a, b = 10)`
- [ ] `has("ide")` or "ide" option
- [ ] reduce number of rules in grammar
- [ ] delayed parsing of if/for/while etc.
- [ ] classic package structure
- [ ] loggers